freddie 0.0.41 → 0.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +85 -11
- package/CHANGELOG.md +16 -0
- package/README.md +2 -2
- package/bin/freddie.js +12 -109
- package/package.json +11 -2
- package/src/acp/server.js +3 -3
- package/src/acp/session.js +8 -8
- package/src/acp/tools.js +5 -4
- package/src/agent/account_usage.js +5 -5
- package/src/agent/credential_sources.js +2 -2
- package/src/agent/curator.js +5 -5
- package/src/agent/machine.js +3 -2
- package/src/agent/manual_compression_feedback.js +5 -5
- package/src/agent/shell_hooks.js +2 -2
- package/src/auth.js +2 -2
- package/src/batch.js +2 -2
- package/src/cli/backup.js +3 -3
- package/src/cli/doctor.js +3 -3
- package/src/cli/dump.js +4 -2
- package/src/cli/env_loader.js +2 -2
- package/src/cli/gateway_cli.js +3 -4
- package/src/cli/hooks.js +2 -2
- package/src/cli/logs.js +4 -4
- package/src/cli/mcp_config.js +2 -2
- package/src/cli/plugins_cmd.js +3 -3
- package/src/cli/status.js +1 -1
- package/src/cli/tools_config.js +2 -2
- package/src/cli/uninstall.js +2 -2
- package/src/config.js +2 -2
- package/src/db.js +3 -3
- package/src/gateway/platforms.js +21 -0
- package/src/home.js +2 -2
- package/src/host/contract.js +39 -0
- package/src/host/host.js +159 -0
- package/src/host/index.js +27 -0
- package/src/index.js +2 -1
- package/src/mcp/server.js +5 -4
- package/src/observability/log.js +2 -2
- package/src/plugins/disk_cleanup/index.js +2 -2
- package/src/plugins/manager.js +13 -63
- package/src/plugins/memory/provider.js +26 -26
- package/src/plugins/observability/index.js +3 -3
- package/src/skills/index.js +2 -2
- package/src/skin/engine.js +2 -2
- package/src/toolsets.js +13 -15
- package/src/web/index.html +1 -1
- package/src/web/server.js +8 -94
- package/src/gateway/platforms/api_server.js +0 -21
- package/src/gateway/platforms/bluebubbles.js +0 -32
- package/src/gateway/platforms/dingtalk.js +0 -32
- package/src/gateway/platforms/discord.js +0 -24
- package/src/gateway/platforms/email.js +0 -51
- package/src/gateway/platforms/feishu.js +0 -32
- package/src/gateway/platforms/feishu_comment.js +0 -12
- package/src/gateway/platforms/feishu_comment_rules.js +0 -11
- package/src/gateway/platforms/homeassistant.js +0 -32
- package/src/gateway/platforms/matrix.js +0 -40
- package/src/gateway/platforms/mattermost.js +0 -29
- package/src/gateway/platforms/qqbot.js +0 -32
- package/src/gateway/platforms/signal.js +0 -33
- package/src/gateway/platforms/slack.js +0 -34
- package/src/gateway/platforms/sms.js +0 -34
- package/src/gateway/platforms/telegram.js +0 -38
- package/src/gateway/platforms/telegram_network.js +0 -17
- package/src/gateway/platforms/webhook.js +0 -19
- package/src/gateway/platforms/wecom.js +0 -32
- package/src/gateway/platforms/wecom_callback.js +0 -15
- package/src/gateway/platforms/wecom_crypto.js +0 -16
- package/src/gateway/platforms/weixin.js +0 -32
- package/src/gateway/platforms/whatsapp.js +0 -40
- package/src/gateway/platforms/yuanbao.js +0 -9
- package/src/gateway/platforms/yuanbao_media.js +0 -5
- package/src/gateway/platforms/yuanbao_proto.js +0 -9
- package/src/gateway/platforms/yuanbao_sticker.js +0 -6
- package/src/plugins/memory/_index.js +0 -8
- package/src/plugins/memory/byterover.js +0 -25
- package/src/plugins/memory/hindsight.js +0 -25
- package/src/plugins/memory/holographic.js +0 -31
- package/src/plugins/memory/honcho.js +0 -25
- package/src/plugins/memory/mem0.js +0 -25
- package/src/plugins/memory/openviking.js +0 -25
- package/src/plugins/memory/retaindb.js +0 -25
- package/src/plugins/memory/supermemory.js +0 -25
- package/src/tools/ansi_strip.js +0 -8
- package/src/tools/approval.js +0 -15
- package/src/tools/bash.js +0 -35
- package/src/tools/binary_extensions.js +0 -22
- package/src/tools/browser.js +0 -48
- package/src/tools/budget_config.js +0 -13
- package/src/tools/checkpoint.js +0 -29
- package/src/tools/clarify.js +0 -15
- package/src/tools/code_execution.js +0 -27
- package/src/tools/credential_files.js +0 -16
- package/src/tools/cronjob.js +0 -16
- package/src/tools/debug_helpers.js +0 -9
- package/src/tools/delegate.js +0 -28
- package/src/tools/discord_tool.js +0 -13
- package/src/tools/edit.js +0 -31
- package/src/tools/env_passthrough.js +0 -15
- package/src/tools/feishu_doc.js +0 -15
- package/src/tools/feishu_drive.js +0 -14
- package/src/tools/file_operations.js +0 -17
- package/src/tools/file_state.js +0 -16
- package/src/tools/file_tools.js +0 -23
- package/src/tools/fuzzy_match.js +0 -8
- package/src/tools/grep.js +0 -51
- package/src/tools/homeassistant_tool.js +0 -15
- package/src/tools/image_gen.js +0 -33
- package/src/tools/interrupt.js +0 -18
- package/src/tools/managed_tool_gateway.js +0 -11
- package/src/tools/mcp_oauth.js +0 -21
- package/src/tools/mcp_oauth_manager.js +0 -20
- package/src/tools/mcp_tool.js +0 -36
- package/src/tools/memory.js +0 -66
- package/src/tools/mixture_of_agents.js +0 -14
- package/src/tools/neutts_synth.js +0 -13
- package/src/tools/openrouter_client.js +0 -13
- package/src/tools/osv_check.js +0 -11
- package/src/tools/patch_parser.js +0 -42
- package/src/tools/path_security.js +0 -16
- package/src/tools/process_registry.js +0 -17
- package/src/tools/read.js +0 -26
- package/src/tools/registry.js +0 -54
- package/src/tools/rl_training.js +0 -13
- package/src/tools/schema_sanitizer.js +0 -18
- package/src/tools/send_message.js +0 -32
- package/src/tools/session_search.js +0 -23
- package/src/tools/skill_manager.js +0 -17
- package/src/tools/skill_usage.js +0 -20
- package/src/tools/skills_guard.js +0 -17
- package/src/tools/skills_hub.js +0 -31
- package/src/tools/skills_index.js +0 -14
- package/src/tools/skills_sync.js +0 -19
- package/src/tools/skills_tool.js +0 -11
- package/src/tools/slash_confirm.js +0 -16
- package/src/tools/terminal.js +0 -29
- package/src/tools/tirith_security.js +0 -25
- package/src/tools/todo.js +0 -54
- package/src/tools/tool_backend_helpers.js +0 -26
- package/src/tools/tool_output_limits.js +0 -15
- package/src/tools/tool_result_storage.js +0 -20
- package/src/tools/transcription.js +0 -19
- package/src/tools/tts.js +0 -19
- package/src/tools/url_safety.js +0 -15
- package/src/tools/vision.js +0 -18
- package/src/tools/voice_mode.js +0 -10
- package/src/tools/web_search.js +0 -37
- package/src/tools/web_tools.js +0 -18
- package/src/tools/website_policy.js +0 -14
- package/src/tools/write.js +0 -25
- package/src/tools/xai_http.js +0 -13
- package/src/tools/yuanbao_tools.js +0 -13
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { getFophHome } from '../home.js'
|
|
4
|
-
import { registry } from './registry.js'
|
|
5
|
-
|
|
6
|
-
function policyPath() { return path.join(getFophHome(), 'policy.json') }
|
|
7
|
-
function loadPolicy() { try { return JSON.parse(fs.readFileSync(policyPath(), 'utf8')) } catch { return { tools: {}, hosts: { allow: [], deny: [] } } } }
|
|
8
|
-
|
|
9
|
-
registry.register({
|
|
10
|
-
name: 'tirith_security',
|
|
11
|
-
toolset: 'core',
|
|
12
|
-
schema: { name: 'tirith_security', description: 'Evaluate a candidate action against ~/.freddie/policy.json. Returns allow|deny|ask.', parameters: { type: 'object', properties: { kind: { type: 'string' }, target: { type: 'string' } }, required: ['kind', 'target'] } },
|
|
13
|
-
handler: async ({ kind, target }) => {
|
|
14
|
-
const p = loadPolicy()
|
|
15
|
-
if (kind === 'tool') {
|
|
16
|
-
const t = p.tools?.[target]
|
|
17
|
-
if (t === 'allow' || t === 'deny') return { decision: t }
|
|
18
|
-
}
|
|
19
|
-
if (kind === 'host') {
|
|
20
|
-
if (p.hosts?.deny?.some(d => target.includes(d))) return { decision: 'deny' }
|
|
21
|
-
if (p.hosts?.allow?.some(d => target.includes(d))) return { decision: 'allow' }
|
|
22
|
-
}
|
|
23
|
-
return { decision: 'ask' }
|
|
24
|
-
},
|
|
25
|
-
})
|
package/src/tools/todo.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { db } from '../db.js'
|
|
2
|
-
import { registry } from './registry.js'
|
|
3
|
-
|
|
4
|
-
async function init() {
|
|
5
|
-
const d = await db()
|
|
6
|
-
d.exec(`CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, content TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created INTEGER NOT NULL, updated INTEGER NOT NULL)`)
|
|
7
|
-
return d
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const ACTIONS = {
|
|
11
|
-
add: async ({ session_id = null, content }) => {
|
|
12
|
-
if (!content) return { error: 'content required' }
|
|
13
|
-
const d = await init(); const now = Date.now()
|
|
14
|
-
const info = d.prepare(`INSERT INTO todos (session_id, content, status, created, updated) VALUES (?, ?, 'pending', ?, ?)`).run(session_id, content, now, now)
|
|
15
|
-
return { id: info.lastInsertRowid, content, status: 'pending' }
|
|
16
|
-
},
|
|
17
|
-
list: async ({ session_id = null }) => {
|
|
18
|
-
const d = await init()
|
|
19
|
-
const rows = session_id ? d.prepare(`SELECT * FROM todos WHERE session_id = ? ORDER BY id DESC`).all(session_id) : d.prepare(`SELECT * FROM todos ORDER BY id DESC`).all()
|
|
20
|
-
return { todos: rows }
|
|
21
|
-
},
|
|
22
|
-
update: async ({ id, status }) => {
|
|
23
|
-
if (!id) return { error: 'id required' }
|
|
24
|
-
(await init()).prepare(`UPDATE todos SET status = ?, updated = ? WHERE id = ?`).run(status, Date.now(), id)
|
|
25
|
-
return { id, status }
|
|
26
|
-
},
|
|
27
|
-
complete: async ({ id }) => ACTIONS.update({ id, status: 'completed' }),
|
|
28
|
-
delete: async ({ id }) => { (await init()).prepare(`DELETE FROM todos WHERE id = ?`).run(id); return { id, deleted: true } },
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
registry.register({
|
|
32
|
-
name: 'todo',
|
|
33
|
-
toolset: 'core',
|
|
34
|
-
schema: {
|
|
35
|
-
name: 'todo',
|
|
36
|
-
description: 'Manage per-session todos. Actions: add, list, update, complete, delete.',
|
|
37
|
-
parameters: {
|
|
38
|
-
type: 'object',
|
|
39
|
-
properties: {
|
|
40
|
-
action: { type: 'string', enum: Object.keys(ACTIONS) },
|
|
41
|
-
content: { type: 'string' },
|
|
42
|
-
id: { type: 'number' },
|
|
43
|
-
status: { type: 'string' },
|
|
44
|
-
session_id: { type: 'string' },
|
|
45
|
-
},
|
|
46
|
-
required: ['action'],
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
handler: async (args) => {
|
|
50
|
-
const fn = ACTIONS[args.action]
|
|
51
|
-
if (!fn) return { error: 'unknown action: ' + args.action }
|
|
52
|
-
return fn(args)
|
|
53
|
-
},
|
|
54
|
-
})
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
|
|
3
|
-
export function shapeArgs(schema, args) {
|
|
4
|
-
if (!schema?.properties) return args
|
|
5
|
-
const out = {}
|
|
6
|
-
for (const [k, def] of Object.entries(schema.properties)) {
|
|
7
|
-
if (k in args) out[k] = args[k]
|
|
8
|
-
else if ('default' in def) out[k] = def.default
|
|
9
|
-
}
|
|
10
|
-
return out
|
|
11
|
-
}
|
|
12
|
-
export function describeTools(filter = null) {
|
|
13
|
-
let list = registry.list()
|
|
14
|
-
if (filter) list = list.filter(t => t.toolset === filter)
|
|
15
|
-
return list.map(t => ({ name: t.name, description: t.schema.description, toolset: t.toolset }))
|
|
16
|
-
}
|
|
17
|
-
registry.register({
|
|
18
|
-
name: 'tool_backend_helpers',
|
|
19
|
-
toolset: 'core',
|
|
20
|
-
schema: { name: 'tool_backend_helpers', description: 'Helper meta-tool: describeTools(filter), shapeArgs(schema, args).', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['describe', 'shape'] }, filter: { type: 'string' }, schema: {}, args: {} }, required: ['action'] } },
|
|
21
|
-
handler: async ({ action, filter, schema, args }) => {
|
|
22
|
-
if (action === 'describe') return { tools: describeTools(filter) }
|
|
23
|
-
if (action === 'shape') return { args: shapeArgs(schema, args || {}) }
|
|
24
|
-
return { error: 'unknown action' }
|
|
25
|
-
},
|
|
26
|
-
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
import { getConfigValue } from '../config.js'
|
|
3
|
-
|
|
4
|
-
export function truncate(s, max = null) {
|
|
5
|
-
const limit = max ?? getConfigValue('tool.output_limit', 100_000)
|
|
6
|
-
const t = String(s)
|
|
7
|
-
if (t.length <= limit) return t
|
|
8
|
-
return t.slice(0, limit) + `\n…[truncated ${t.length - limit} chars]`
|
|
9
|
-
}
|
|
10
|
-
registry.register({
|
|
11
|
-
name: 'tool_output_limits',
|
|
12
|
-
toolset: 'core',
|
|
13
|
-
schema: { name: 'tool_output_limits', description: 'Apply the configured output truncation cap to a string.', parameters: { type: 'object', properties: { text: { type: 'string' }, max: { type: 'number' } }, required: ['text'] } },
|
|
14
|
-
handler: async ({ text, max }) => ({ text: truncate(text, max) }),
|
|
15
|
-
})
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import crypto from 'node:crypto'
|
|
4
|
-
import { getFophHome } from '../home.js'
|
|
5
|
-
import { registry } from './registry.js'
|
|
6
|
-
|
|
7
|
-
function dir() { const d = path.join(getFophHome(), 'tool-results'); fs.mkdirSync(d, { recursive: true }); return d }
|
|
8
|
-
|
|
9
|
-
registry.register({
|
|
10
|
-
name: 'tool_result_storage',
|
|
11
|
-
toolset: 'core',
|
|
12
|
-
schema: { name: 'tool_result_storage', description: 'Persist a large tool result to disk; return reference token. Actions: store, fetch, list, delete.', parameters: { type: 'object', properties: { action: { type: 'string', enum: ['store', 'fetch', 'list', 'delete'] }, content: { type: 'string' }, token: { type: 'string' } }, required: ['action'] } },
|
|
13
|
-
handler: async ({ action, content, token }) => {
|
|
14
|
-
if (action === 'store') { const t = crypto.randomBytes(8).toString('hex'); fs.writeFileSync(path.join(dir(), t + '.txt'), content || '', 'utf8'); return { token: t, bytes: (content || '').length } }
|
|
15
|
-
if (action === 'fetch') { const f = path.join(dir(), token + '.txt'); return fs.existsSync(f) ? { content: fs.readFileSync(f, 'utf8') } : { error: 'not found' } }
|
|
16
|
-
if (action === 'list') return { tokens: fs.readdirSync(dir()).filter(f => f.endsWith('.txt')).map(f => f.replace(/\.txt$/, '')) }
|
|
17
|
-
if (action === 'delete') { const f = path.join(dir(), token + '.txt'); if (fs.existsSync(f)) fs.unlinkSync(f); return { deleted: token } }
|
|
18
|
-
return { error: 'unknown action' }
|
|
19
|
-
},
|
|
20
|
-
})
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import { registry } from './registry.js'
|
|
3
|
-
registry.register({
|
|
4
|
-
name: 'transcription',
|
|
5
|
-
toolset: 'creative',
|
|
6
|
-
schema: { name: 'transcription', description: 'Transcribe audio with OpenAI Whisper.', parameters: { type: 'object', properties: { file_path: { type: 'string' }, model: { type: 'string', default: 'whisper-1' } }, required: ['file_path'] } },
|
|
7
|
-
requiresEnv: ['OPENAI_API_KEY'],
|
|
8
|
-
checkFn: () => Boolean(process.env.OPENAI_API_KEY),
|
|
9
|
-
handler: async ({ file_path, model = 'whisper-1' }) => {
|
|
10
|
-
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
11
|
-
if (!fs.existsSync(file_path)) return { error: 'file not found: ' + file_path }
|
|
12
|
-
const blob = new Blob([fs.readFileSync(file_path)])
|
|
13
|
-
const fd = new FormData()
|
|
14
|
-
fd.append('file', blob, file_path.split(/[\\/]/).pop())
|
|
15
|
-
fd.append('model', model)
|
|
16
|
-
const r = await fetch('https://api.openai.com/v1/audio/transcriptions', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: fd })
|
|
17
|
-
return await r.json()
|
|
18
|
-
},
|
|
19
|
-
})
|
package/src/tools/tts.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
registry.register({
|
|
3
|
-
name: 'tts',
|
|
4
|
-
toolset: 'creative',
|
|
5
|
-
schema: { name: 'tts', description: 'Synthesize speech (OpenAI tts-1 or ElevenLabs).', parameters: { type: 'object', properties: { text: { type: 'string' }, provider: { type: 'string', enum: ['openai', 'elevenlabs'], default: 'openai' }, voice: { type: 'string' } }, required: ['text'] } },
|
|
6
|
-
requiresEnv: ['OPENAI_API_KEY or ELEVENLABS_API_KEY'],
|
|
7
|
-
checkFn: () => Boolean(process.env.OPENAI_API_KEY || process.env.ELEVENLABS_API_KEY),
|
|
8
|
-
handler: async ({ text, provider = 'openai', voice = 'alloy' }) => {
|
|
9
|
-
if (provider === 'elevenlabs') {
|
|
10
|
-
if (!process.env.ELEVENLABS_API_KEY) return { error: 'ELEVENLABS_API_KEY required' }
|
|
11
|
-
const v = voice || '21m00Tcm4TlvDq8ikWAM'
|
|
12
|
-
const r = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${v}`, { method: 'POST', headers: { 'xi-api-key': process.env.ELEVENLABS_API_KEY, 'content-type': 'application/json' }, body: JSON.stringify({ text }) })
|
|
13
|
-
return { status: r.status, contentType: r.headers.get('content-type'), bytes: (await r.arrayBuffer()).byteLength }
|
|
14
|
-
}
|
|
15
|
-
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
16
|
-
const r = await fetch('https://api.openai.com/v1/audio/speech', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model: 'tts-1', input: text, voice }) })
|
|
17
|
-
return { status: r.status, bytes: (await r.arrayBuffer()).byteLength }
|
|
18
|
-
},
|
|
19
|
-
})
|
package/src/tools/url_safety.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
const SUSPICIOUS = ['phish', 'malware', '.onion']
|
|
3
|
-
const PRIVATE_RANGES = [/^10\./, /^192\.168\./, /^172\.(1[6-9]|2\d|3[01])\./, /^127\./, /^0\./, /^169\.254\./]
|
|
4
|
-
registry.register({
|
|
5
|
-
name: 'url_safety',
|
|
6
|
-
toolset: 'core',
|
|
7
|
-
schema: { name: 'url_safety', description: 'Heuristic URL safety check (private IPs, known-bad TLDs, scheme).', parameters: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] } },
|
|
8
|
-
handler: async ({ url }) => {
|
|
9
|
-
let u; try { u = new URL(url) } catch { return { safe: false, reason: 'invalid URL' } }
|
|
10
|
-
if (!['http:', 'https:'].includes(u.protocol)) return { safe: false, reason: 'unsupported scheme: ' + u.protocol }
|
|
11
|
-
if (PRIVATE_RANGES.some(re => re.test(u.hostname))) return { safe: false, reason: 'private IP host' }
|
|
12
|
-
for (const s of SUSPICIOUS) if (u.hostname.includes(s)) return { safe: false, reason: 'suspicious token: ' + s }
|
|
13
|
-
return { safe: true, host: u.hostname }
|
|
14
|
-
},
|
|
15
|
-
})
|
package/src/tools/vision.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
registry.register({
|
|
3
|
-
name: 'vision',
|
|
4
|
-
toolset: 'creative',
|
|
5
|
-
schema: { name: 'vision', description: 'Describe an image (URL or base64) using a vision-capable LLM.', parameters: { type: 'object', properties: { image_url: { type: 'string' }, prompt: { type: 'string', default: 'Describe this image.' }, provider: { type: 'string', enum: ['openai', 'anthropic'], default: 'openai' } }, required: ['image_url'] } },
|
|
6
|
-
requiresEnv: ['OPENAI_API_KEY or ANTHROPIC_API_KEY'],
|
|
7
|
-
checkFn: () => Boolean(process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY),
|
|
8
|
-
handler: async ({ image_url, prompt = 'Describe this image.', provider = 'openai' }) => {
|
|
9
|
-
if (provider === 'anthropic') {
|
|
10
|
-
if (!process.env.ANTHROPIC_API_KEY) return { error: 'ANTHROPIC_API_KEY required' }
|
|
11
|
-
const r = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': process.env.ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01', 'content-type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-6', max_tokens: 1024, messages: [{ role: 'user', content: [{ type: 'image', source: { type: 'url', url: image_url } }, { type: 'text', text: prompt }] }] }) })
|
|
12
|
-
return await r.json()
|
|
13
|
-
}
|
|
14
|
-
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
15
|
-
const r = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: [{ type: 'text', text: prompt }, { type: 'image_url', image_url: { url: image_url } }] }] }) })
|
|
16
|
-
return await r.json()
|
|
17
|
-
},
|
|
18
|
-
})
|
package/src/tools/voice_mode.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
registry.register({
|
|
3
|
-
name: 'voice_mode',
|
|
4
|
-
toolset: 'creative',
|
|
5
|
-
schema: { name: 'voice_mode', description: 'Toggle full-duplex voice (transcription in + tts out) for the active session. Returns the new state.', parameters: { type: 'object', properties: { enabled: { type: 'boolean' } } } },
|
|
6
|
-
handler: async ({ enabled }, ctx = {}) => {
|
|
7
|
-
if (typeof ctx.setVoiceMode === 'function') return await ctx.setVoiceMode(Boolean(enabled))
|
|
8
|
-
return { enabled: Boolean(enabled), note: 'voice mode toggled in-process; bind ctx.setVoiceMode to wire to a real audio loop' }
|
|
9
|
-
},
|
|
10
|
-
})
|
package/src/tools/web_search.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
|
|
3
|
-
registry.register({
|
|
4
|
-
name: 'web_search',
|
|
5
|
-
toolset: 'browse',
|
|
6
|
-
schema: {
|
|
7
|
-
name: 'web_search',
|
|
8
|
-
description: 'Search the web (DuckDuckGo HTML or SerpAPI). Returns title/url/snippet list.',
|
|
9
|
-
parameters: {
|
|
10
|
-
type: 'object',
|
|
11
|
-
properties: {
|
|
12
|
-
query: { type: 'string' },
|
|
13
|
-
limit: { type: 'number', default: 5 },
|
|
14
|
-
},
|
|
15
|
-
required: ['query'],
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
checkFn: () => true,
|
|
19
|
-
requiresEnv: ['SERPAPI_KEY (optional, falls back to DDG)'],
|
|
20
|
-
handler: async ({ query, limit = 5 }) => {
|
|
21
|
-
if (process.env.SERPAPI_KEY) {
|
|
22
|
-
const url = `https://serpapi.com/search.json?q=${encodeURIComponent(query)}&api_key=${process.env.SERPAPI_KEY}`
|
|
23
|
-
const data = await fetch(url).then(r => r.json())
|
|
24
|
-
const results = (data.organic_results || []).slice(0, limit).map(r => ({ title: r.title, url: r.link, snippet: r.snippet }))
|
|
25
|
-
return { results }
|
|
26
|
-
}
|
|
27
|
-
const fetchFn = globalThis.__fophFetch || fetch
|
|
28
|
-
const html = await fetchFn(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`).then(r => r.text())
|
|
29
|
-
const results = []
|
|
30
|
-
const re = /<a class="result__a"[^>]*href="([^"]+)"[^>]*>([^<]+)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([^<]+)<\/a>/g
|
|
31
|
-
let m
|
|
32
|
-
while ((m = re.exec(html)) && results.length < limit) {
|
|
33
|
-
results.push({ url: m[1], title: m[2].replace(/&/g, '&'), snippet: m[3].replace(/<\/?b>/g, '') })
|
|
34
|
-
}
|
|
35
|
-
return { results }
|
|
36
|
-
},
|
|
37
|
-
})
|
package/src/tools/web_tools.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
registry.register({
|
|
3
|
-
name: 'web_fetch',
|
|
4
|
-
toolset: 'browse',
|
|
5
|
-
schema: { name: 'web_fetch', description: 'Fetch a URL and return text/json/headers.', parameters: { type: 'object', properties: { url: { type: 'string' }, method: { type: 'string', default: 'GET' }, headers: {}, body: { type: 'string' }, parse: { type: 'string', enum: ['text', 'json'] } }, required: ['url'] } },
|
|
6
|
-
handler: async ({ url, method = 'GET', headers = {}, body, parse = 'text' }) => {
|
|
7
|
-
const r = await fetch(url, { method, headers, body })
|
|
8
|
-
const ct = r.headers.get('content-type')
|
|
9
|
-
const out = parse === 'json' ? await r.json().catch(() => null) : await r.text()
|
|
10
|
-
return { status: r.status, contentType: ct, body: out }
|
|
11
|
-
},
|
|
12
|
-
})
|
|
13
|
-
registry.register({
|
|
14
|
-
name: 'web_extract',
|
|
15
|
-
toolset: 'browse',
|
|
16
|
-
schema: { name: 'web_extract', description: 'Strip tags from HTML to plain text.', parameters: { type: 'object', properties: { html: { type: 'string' } }, required: ['html'] } },
|
|
17
|
-
handler: async ({ html }) => ({ text: String(html).replace(/<script[\s\S]*?<\/script>/gi, '').replace(/<style[\s\S]*?<\/style>/gi, '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim() }),
|
|
18
|
-
})
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
import { getConfigValue } from '../config.js'
|
|
3
|
-
registry.register({
|
|
4
|
-
name: 'website_policy',
|
|
5
|
-
toolset: 'core',
|
|
6
|
-
schema: { name: 'website_policy', description: 'Per-host fetch policy (rate limit, allow/deny). Reads config.website_policy.', parameters: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] } },
|
|
7
|
-
handler: async ({ url }) => {
|
|
8
|
-
const policy = getConfigValue('website_policy', { allow: [], deny: [], ratelimit_ms: 1000 }) || {}
|
|
9
|
-
const u = new URL(url)
|
|
10
|
-
if (policy.deny?.some(d => u.hostname.includes(d))) return { decision: 'deny' }
|
|
11
|
-
if (policy.allow?.length && !policy.allow.some(a => u.hostname.includes(a))) return { decision: 'deny', reason: 'not in allow list' }
|
|
12
|
-
return { decision: 'allow', ratelimit_ms: policy.ratelimit_ms || 1000 }
|
|
13
|
-
},
|
|
14
|
-
})
|
package/src/tools/write.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
import { registry } from './registry.js'
|
|
4
|
-
|
|
5
|
-
registry.register({
|
|
6
|
-
name: 'write',
|
|
7
|
-
toolset: 'core',
|
|
8
|
-
schema: {
|
|
9
|
-
name: 'write',
|
|
10
|
-
description: 'Write content to a file (overwrites).',
|
|
11
|
-
parameters: {
|
|
12
|
-
type: 'object',
|
|
13
|
-
properties: {
|
|
14
|
-
path: { type: 'string' },
|
|
15
|
-
content: { type: 'string' },
|
|
16
|
-
},
|
|
17
|
-
required: ['path', 'content'],
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
handler: async ({ path: p, content }) => {
|
|
21
|
-
fs.mkdirSync(path.dirname(p), { recursive: true })
|
|
22
|
-
fs.writeFileSync(p, content, 'utf8')
|
|
23
|
-
return { path: p, bytes: Buffer.byteLength(content, 'utf8') }
|
|
24
|
-
},
|
|
25
|
-
})
|
package/src/tools/xai_http.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
registry.register({
|
|
3
|
-
name: 'xai_grok',
|
|
4
|
-
toolset: 'core',
|
|
5
|
-
schema: { name: 'xai_grok', description: 'Chat completion via xAI Grok.', parameters: { type: 'object', properties: { prompt: { type: 'string' }, model: { type: 'string', default: 'grok-3' } }, required: ['prompt'] } },
|
|
6
|
-
requiresEnv: ['XAI_API_KEY'],
|
|
7
|
-
checkFn: () => Boolean(process.env.XAI_API_KEY),
|
|
8
|
-
handler: async ({ prompt, model = 'grok-3' }) => {
|
|
9
|
-
if (!process.env.XAI_API_KEY) return { error: 'XAI_API_KEY required' }
|
|
10
|
-
const r = await fetch('https://api.x.ai/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.XAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }) })
|
|
11
|
-
return await r.json()
|
|
12
|
-
},
|
|
13
|
-
})
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { registry } from './registry.js'
|
|
2
|
-
registry.register({
|
|
3
|
-
name: 'yuanbao_tools',
|
|
4
|
-
toolset: 'core',
|
|
5
|
-
schema: { name: 'yuanbao_tools', description: 'Tencent Yuanbao chat completion (Hunyuan).', parameters: { type: 'object', properties: { prompt: { type: 'string' }, model: { type: 'string', default: 'hunyuan-pro' } }, required: ['prompt'] } },
|
|
6
|
-
requiresEnv: ['YUANBAO_API_KEY'],
|
|
7
|
-
checkFn: () => Boolean(process.env.YUANBAO_API_KEY),
|
|
8
|
-
handler: async ({ prompt, model = 'hunyuan-pro' }) => {
|
|
9
|
-
if (!process.env.YUANBAO_API_KEY) return { error: 'YUANBAO_API_KEY required' }
|
|
10
|
-
const r = await fetch('https://api.hunyuan.cloud.tencent.com/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.YUANBAO_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }) })
|
|
11
|
-
return await r.json()
|
|
12
|
-
},
|
|
13
|
-
})
|