thinyai 0.1.5 → 0.1.7
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/dist/bin.js +135 -9
- package/package.json +5 -4
package/dist/bin.js
CHANGED
|
@@ -11,7 +11,7 @@ import { stdin, stdout } from "process";
|
|
|
11
11
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
12
12
|
import { homedir as homedir2 } from "os";
|
|
13
13
|
import { join as join2 } from "path";
|
|
14
|
-
import { z as
|
|
14
|
+
import { z as z7 } from "zod";
|
|
15
15
|
|
|
16
16
|
// ../../packages/core/src/domain/messages.ts
|
|
17
17
|
var systemMessage = (content) => ({ role: "system", content });
|
|
@@ -1608,6 +1608,61 @@ async function mcpHttpPlugin(opts) {
|
|
|
1608
1608
|
return buildMcpPlugin(transport, opts.name ?? "mcp");
|
|
1609
1609
|
}
|
|
1610
1610
|
|
|
1611
|
+
// ../../packages/plugins/web-search/src/index.ts
|
|
1612
|
+
import { z as z6 } from "zod";
|
|
1613
|
+
function describeHttpError(status) {
|
|
1614
|
+
if (status === 401) return "Authentication failed \u2014 verify your BRAVE_API_KEY is correct.";
|
|
1615
|
+
if (status === 403) return "Access forbidden \u2014 check your API subscription level.";
|
|
1616
|
+
if (status === 422) return "Invalid query \u2014 the search request was malformed.";
|
|
1617
|
+
if (status === 429) return "Rate limit exceeded \u2014 slow down requests or upgrade your plan.";
|
|
1618
|
+
if (status >= 500) return "Brave Search server error \u2014 try again later.";
|
|
1619
|
+
return `Unexpected error \u2014 see Brave Search API docs for HTTP ${String(status)}.`;
|
|
1620
|
+
}
|
|
1621
|
+
function webSearchPlugin(opts) {
|
|
1622
|
+
if (!opts.apiKey.trim()) {
|
|
1623
|
+
throw new Error(
|
|
1624
|
+
"webSearchPlugin: apiKey is required. Obtain a key at https://brave.com/search/api/ and set BRAVE_API_KEY in .env."
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
const endpoint = opts.endpoint ?? "https://api.search.brave.com/res/v1/web/search";
|
|
1628
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
1629
|
+
return {
|
|
1630
|
+
name: "web-search",
|
|
1631
|
+
tools: [
|
|
1632
|
+
defineTool({
|
|
1633
|
+
name: "web_search",
|
|
1634
|
+
description: "Search the public web and return the top results (title, url, snippet). Use when asked about current events, recent facts, or any topic you are not certain about.",
|
|
1635
|
+
parameters: z6.object({
|
|
1636
|
+
query: z6.string().min(1).describe("The search query."),
|
|
1637
|
+
count: z6.number().int().min(1).max(10).default(5).describe("Number of results to return (1\u201310).")
|
|
1638
|
+
}),
|
|
1639
|
+
execute: async ({ query, count }) => {
|
|
1640
|
+
const url = `${endpoint}?q=${encodeURIComponent(query)}&count=${String(count)}`;
|
|
1641
|
+
const response = await fetchImpl(url, {
|
|
1642
|
+
headers: {
|
|
1643
|
+
Accept: "application/json",
|
|
1644
|
+
"X-Subscription-Token": opts.apiKey
|
|
1645
|
+
}
|
|
1646
|
+
});
|
|
1647
|
+
if (!response.ok) {
|
|
1648
|
+
throw new Error(
|
|
1649
|
+
`web_search failed: HTTP ${String(response.status)} \u2014 ${describeHttpError(response.status)}`
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
const data = await response.json();
|
|
1653
|
+
return {
|
|
1654
|
+
results: (data.web?.results ?? []).map((result) => ({
|
|
1655
|
+
title: result.title,
|
|
1656
|
+
url: result.url,
|
|
1657
|
+
snippet: result.description
|
|
1658
|
+
}))
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
})
|
|
1662
|
+
]
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1611
1666
|
// ../../packages/skills/src/catalog.ts
|
|
1612
1667
|
var BUILTIN_SKILLS = [
|
|
1613
1668
|
{
|
|
@@ -2228,7 +2283,7 @@ function captureStats(base, turn) {
|
|
|
2228
2283
|
var echoTool = defineTool({
|
|
2229
2284
|
name: "echo",
|
|
2230
2285
|
description: "Echo text back verbatim. Use when asked to repeat or echo something.",
|
|
2231
|
-
parameters:
|
|
2286
|
+
parameters: z7.object({ text: z7.string().describe("the text to echo") }),
|
|
2232
2287
|
execute: ({ text: text2 }) => Promise.resolve({ echoed: text2 })
|
|
2233
2288
|
});
|
|
2234
2289
|
function parseSkillArgs() {
|
|
@@ -2327,11 +2382,11 @@ async function runCli() {
|
|
|
2327
2382
|
name: "sui_setup",
|
|
2328
2383
|
description: "Set up or change the agent's Sui wallet so it can read balances and sign transactions. Use when the user asks to enable Sui, create/import a wallet, or switch network. Modes: generate (new local key), import (a suiprivkey\u2026), rill (use a Rill MCP signer URL). Takes effect immediately; Rill's builder tools connect on the next start. Always remind the user to fund the returned address.",
|
|
2329
2384
|
sensitive: true,
|
|
2330
|
-
parameters:
|
|
2331
|
-
network:
|
|
2332
|
-
wallet:
|
|
2333
|
-
secretKey:
|
|
2334
|
-
rillMcpUrl:
|
|
2385
|
+
parameters: z7.object({
|
|
2386
|
+
network: z7.enum(["testnet", "mainnet"]).default("testnet"),
|
|
2387
|
+
wallet: z7.enum(["generate", "import", "rill"]).describe("generate a new key, import a suiprivkey\u2026, or use a Rill MCP signer"),
|
|
2388
|
+
secretKey: z7.string().optional().describe("suiprivkey\u2026 \u2014 required when wallet=import"),
|
|
2389
|
+
rillMcpUrl: z7.string().optional().describe("Rill MCP URL \u2014 used when wallet=rill")
|
|
2335
2390
|
}),
|
|
2336
2391
|
execute: async ({ network: net, wallet, secretKey, rillMcpUrl }) => {
|
|
2337
2392
|
const network2 = net === "mainnet" ? "mainnet" : "testnet";
|
|
@@ -2366,13 +2421,79 @@ async function runCli() {
|
|
|
2366
2421
|
};
|
|
2367
2422
|
}
|
|
2368
2423
|
});
|
|
2424
|
+
const fetchUrlTool = defineTool({
|
|
2425
|
+
name: "fetch_url",
|
|
2426
|
+
description: "Fetch the contents of an http(s) URL (markdown, text, JSON, HTML). ALWAYS use this when the user shares a link \u2014 e.g. a skill.md, docs page, or an API/MCP endpoint \u2014 instead of saying you can't open URLs. Returns the response text (truncated if very large).",
|
|
2427
|
+
parameters: z7.object({
|
|
2428
|
+
url: z7.string().url().describe("The http(s) URL to fetch."),
|
|
2429
|
+
maxChars: z7.number().int().positive().optional().describe("Max characters of body to return (default 20000).")
|
|
2430
|
+
}),
|
|
2431
|
+
execute: async ({ url, maxChars }) => {
|
|
2432
|
+
const limit = maxChars ?? 2e4;
|
|
2433
|
+
const res = await fetch(url, {
|
|
2434
|
+
headers: { "user-agent": "thiny-cli", accept: "*/*" },
|
|
2435
|
+
signal: AbortSignal.timeout(15e3)
|
|
2436
|
+
});
|
|
2437
|
+
const body = await res.text();
|
|
2438
|
+
return {
|
|
2439
|
+
url,
|
|
2440
|
+
status: res.status,
|
|
2441
|
+
contentType: res.headers.get("content-type") ?? "",
|
|
2442
|
+
truncated: body.length > limit,
|
|
2443
|
+
content: body.slice(0, limit)
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
const exaKey = process.env.EXA_API_KEY;
|
|
2448
|
+
const webPlugins = [];
|
|
2449
|
+
const webTools = [];
|
|
2450
|
+
if (exaKey) {
|
|
2451
|
+
webTools.push(
|
|
2452
|
+
defineTool({
|
|
2453
|
+
name: "web_search",
|
|
2454
|
+
description: "Search the WEB via Exa and get ranked results with text snippets. Use whenever you need current info, prices, docs, or to find a page \u2014 anything you don't already know. This is DIFFERENT from fetch_url: web_search finds pages by query; fetch_url reads one URL you have.",
|
|
2455
|
+
parameters: z7.object({
|
|
2456
|
+
query: z7.string().min(1).describe("The search query."),
|
|
2457
|
+
numResults: z7.number().int().positive().optional().describe("Results to return (default 5).")
|
|
2458
|
+
}),
|
|
2459
|
+
execute: async ({ query, numResults }) => {
|
|
2460
|
+
const res = await fetch("https://api.exa.ai/search", {
|
|
2461
|
+
method: "POST",
|
|
2462
|
+
headers: { "content-type": "application/json", "x-api-key": exaKey },
|
|
2463
|
+
body: JSON.stringify({
|
|
2464
|
+
query,
|
|
2465
|
+
numResults: numResults ?? 5,
|
|
2466
|
+
contents: { text: { maxCharacters: 1200 } }
|
|
2467
|
+
}),
|
|
2468
|
+
signal: AbortSignal.timeout(2e4)
|
|
2469
|
+
});
|
|
2470
|
+
if (!res.ok) throw new Error(`web_search: Exa HTTP ${String(res.status)} ${await res.text()}`);
|
|
2471
|
+
const data = await res.json();
|
|
2472
|
+
return {
|
|
2473
|
+
query,
|
|
2474
|
+
results: (data.results ?? []).map((r) => ({ title: r.title, url: r.url, text: r.text }))
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
})
|
|
2478
|
+
);
|
|
2479
|
+
} else if (process.env.BRAVE_API_KEY) {
|
|
2480
|
+
webPlugins.push(webSearchPlugin({ apiKey: process.env.BRAVE_API_KEY }));
|
|
2481
|
+
}
|
|
2482
|
+
const webSearchOn = webTools.length > 0 || webPlugins.length > 0;
|
|
2369
2483
|
const budget = budgetMiddleware({ maxCalls: 50, logger });
|
|
2370
2484
|
const agent = await createAgent({
|
|
2371
2485
|
model,
|
|
2372
2486
|
logger: agentLogger,
|
|
2373
2487
|
persona,
|
|
2374
|
-
systemPrompt:
|
|
2375
|
-
|
|
2488
|
+
systemPrompt: `You are ${persona?.name ?? "ThinyAI"}, a capable assistant with real tools. Be concise.
|
|
2489
|
+
|
|
2490
|
+
HOW TO ACT: When a request maps to one of your tools, CALL THE TOOL automatically \u2014 figure out the right tool yourself; do not ask the user which tool to run, do not ask permission for read-only actions, and never say you can't do something one of your tools covers. Chain tools when needed (e.g. web_search \u2192 fetch_url \u2192 act).
|
|
2491
|
+
|
|
2492
|
+
YOUR TOOLS:
|
|
2493
|
+
\u2022 Memory \u2014 remember_fact, recall_memory: durable memory across sessions (stored on Walrus). Known facts are injected each turn under \u201C[User Memory \u2026]\u201D. Immediately save anything durable the user shares (name, role, preferences, projects, goals). Answer \u201Cwhat do you remember\u201D from it. You DO remember across sessions \u2014 never say otherwise.
|
|
2494
|
+
\u2022 Links \u2014 fetch_url: read ANY URL the user shares (a skill.md, docs, JSON, an API/MCP endpoint). Always fetch shared links instead of saying you can't open URLs.
|
|
2495
|
+
` + (webSearchOn ? "\u2022 Web search \u2014 web_search: search the web for anything you don't know (news, prices, docs). web_search FINDS pages by query; fetch_url READS a specific URL \u2014 use them together.\n" : "") + "\u2022 Planning \u2014 update_plan (track multi-step work), delegate_task (hand a focused subtask to a sub-agent).\n\u2022 Sui blockchain \u2014 you transact yourself; NEVER tell the user to install a browser wallet. " + (suiSignerRef ? `A wallet IS set up on ${suiNetwork} at ${suiSignerRef.address ?? "?"}. ` : "No wallet yet \u2014 call sui_setup (generate / import / rill) when the user wants Sui, then have them fund the address. ") + "sui_setup (create/import a wallet), sui_balance & sui_object (read), sui_transfer (send SUI or any coin \u2014 amounts in MIST, 1 SUI = 1e9), sui_move_call (call ANY Move function \u2014 any on-chain action), sui_execute_ptb (sign a PTB a builder/Rill produced). Prefer sui_transfer for sends and sui_move_call for contract calls; confirm details before signing.",
|
|
2496
|
+
tools: [echoTool, suiSetupTool, fetchUrlTool, ...webTools],
|
|
2376
2497
|
plugins: [
|
|
2377
2498
|
{
|
|
2378
2499
|
name: "observability",
|
|
@@ -2385,6 +2506,8 @@ async function runCli() {
|
|
|
2385
2506
|
// always present; tools guide setup if no wallet
|
|
2386
2507
|
...suiPlugins,
|
|
2387
2508
|
// Rill MCP builder tools (if connected)
|
|
2509
|
+
...webPlugins,
|
|
2510
|
+
// web_search when BRAVE_API_KEY is set
|
|
2388
2511
|
...skillPlugins
|
|
2389
2512
|
]
|
|
2390
2513
|
});
|
|
@@ -2430,6 +2553,9 @@ async function runCli() {
|
|
|
2430
2553
|
`Sui: ${suiNetwork} \xB7 ${suiSignerRef.address ?? "?"}${process.env.MCP_URL ? " \xB7 Rill MCP connected" : ""}`
|
|
2431
2554
|
);
|
|
2432
2555
|
else renderInfo("Sui: no wallet \u2014 ask the agent to set one up, or run `thiny sui init`");
|
|
2556
|
+
renderInfo(
|
|
2557
|
+
`Web: fetch_url (any URL)${webSearchOn ? ` \xB7 web_search (${exaKey ? "Exa" : "Brave"})` : " \xB7 web_search off (set EXA_API_KEY)"}`
|
|
2558
|
+
);
|
|
2433
2559
|
notifyIfUpdate(thinyDir);
|
|
2434
2560
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
2435
2561
|
emitKeypressEvents(stdin);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinyai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Thiny AI — a beautiful terminal agent: interactive chat, tools, Walrus memory, and Sui execution.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,13 +38,14 @@
|
|
|
38
38
|
"tsup": "^8.5.1",
|
|
39
39
|
"typescript": "^5.5.0",
|
|
40
40
|
"@thiny/core": "0.1.0",
|
|
41
|
-
"@thiny/mcp": "0.1.0",
|
|
42
41
|
"@thiny/logger-pino": "0.1.0",
|
|
43
|
-
"@thiny/walrus": "0.1.0",
|
|
44
|
-
"@thiny/model-aisdk": "0.1.0",
|
|
45
42
|
"@thiny/plugin-agents": "0.1.0",
|
|
46
43
|
"@thiny/memory-memwal": "0.1.0",
|
|
44
|
+
"@thiny/mcp": "0.1.0",
|
|
45
|
+
"@thiny/walrus": "0.1.0",
|
|
47
46
|
"@thiny/signer-sui": "0.1.0",
|
|
47
|
+
"@thiny/plugin-web-search": "0.1.0",
|
|
48
|
+
"@thiny/model-aisdk": "0.1.0",
|
|
48
49
|
"@thiny/plugin-sui": "0.1.0",
|
|
49
50
|
"@thiny/skills": "0.1.0"
|
|
50
51
|
},
|