cortico 0.1.0

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.
Files changed (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,261 @@
1
+ /**
2
+ * BraveClient:Brave Search API的最小客户端(docs/Brave_Search_API.md)。
3
+ * 按 web_search 工具的 mode 分派到不同端点,统一映射成"带编号来源的证据"
4
+ * (title/url/snippet)。retry/timeout骨架:429/5xx/超时退避重试,其余错误直抛。
5
+ */
6
+ import type { Logger } from '../../core/types.ts';
7
+ import { nullLogger } from '../../core/util.ts';
8
+
9
+ export type SearchMode = 'auto' | 'answer' | 'links' | 'news' | 'images' | 'videos';
10
+ export type Freshness = 'any' | 'day' | 'week' | 'month' | 'year';
11
+ type SafeSearch = 'off' | 'moderate' | 'strict';
12
+
13
+ interface BraveResult {
14
+ title: string;
15
+ url: string;
16
+ snippet: string;
17
+ }
18
+
19
+ interface BraveSearchOptions {
20
+ mode: SearchMode;
21
+ fresh: Freshness;
22
+ /** 期望条数(1-6,由工具层校验范围) */
23
+ n: number;
24
+ }
25
+
26
+ interface BraveSearchOutcome {
27
+ /** 实际打的端点(answer遇OPTION_NOT_IN_PLAN会降级到web/search,这里如实标注) */
28
+ endpoint: string;
29
+ results: BraveResult[];
30
+ }
31
+
32
+ export interface BraveClientConfig {
33
+ apiKey: string;
34
+ country: string;
35
+ searchLang: string;
36
+ uiLang: string;
37
+ safesearch: SafeSearch;
38
+ timeoutMs: number;
39
+ }
40
+
41
+ export class BraveApiError extends Error {
42
+ status: number;
43
+ code?: string;
44
+ constructor(message: string, status: number, code?: string) {
45
+ super(message);
46
+ this.name = 'BraveApiError';
47
+ this.status = status;
48
+ this.code = code;
49
+ }
50
+ }
51
+
52
+ const BASE_URL = 'https://api.search.brave.com/res/v1';
53
+ /** HTTP 429 的有限退避间隔。 */
54
+ const RETRY_DELAYS_MS = [1000, 3000];
55
+
56
+ const FRESH_MAP: Record<Freshness, string | undefined> = {
57
+ any: undefined,
58
+ day: 'pd',
59
+ week: 'pw',
60
+ month: 'pm',
61
+ year: 'py',
62
+ };
63
+
64
+ /** 去掉<strong>高亮标记、压缩空白、按长度截断(工具描述承诺"精简") */
65
+ function cleanText(s: unknown, max = 220): string {
66
+ const t = (typeof s === 'string' ? s : '').replace(/<\/?strong>/g, '').replace(/\s+/g, ' ').trim();
67
+ return t.length > max ? `${t.slice(0, max - 1)}…` : t;
68
+ }
69
+
70
+
71
+ interface RawWebResult { title?: string; url?: string; description?: string }
72
+ interface WebSearchResponse { web?: { results?: RawWebResult[] } }
73
+ interface NewsSearchResponse { results?: RawWebResult[] }
74
+ interface RawVideoResult extends RawWebResult { video?: { duration?: string; publisher?: string } }
75
+ interface VideoSearchResponse { results?: RawVideoResult[] }
76
+ interface RawImageResult { title?: string; url?: string; source?: string; properties?: { url?: string } }
77
+ interface ImageSearchResponse { results?: RawImageResult[] }
78
+ interface RawGrounding { url?: string; title?: string; snippets?: string[] }
79
+ interface LlmContextResponse {
80
+ grounding?: { generic?: RawGrounding[] };
81
+ sources?: Record<string, { title?: string }>;
82
+ }
83
+
84
+ export class BraveClient {
85
+ private readonly cfg: BraveClientConfig;
86
+ private readonly log: Logger;
87
+
88
+ constructor(cfg: BraveClientConfig, log: Logger = nullLogger()) {
89
+ this.cfg = cfg;
90
+ this.log = log;
91
+ }
92
+
93
+ async search(q: string, opts: BraveSearchOptions): Promise<BraveSearchOutcome> {
94
+ const mode = opts.mode === 'auto' ? 'links' : opts.mode;
95
+ switch (mode) {
96
+ case 'answer':
97
+ try {
98
+ return { endpoint: 'llm/context', results: await this.llmContext(q, opts.n, opts.fresh) };
99
+ } catch (e) {
100
+ if (e instanceof BraveApiError && e.code === 'OPTION_NOT_IN_PLAN') {
101
+ this.log.warn('llm/context不在套餐内,降级web/search');
102
+ return {
103
+ endpoint: 'web/search (llm/context not in plan)',
104
+ results: await this.webSearch(q, opts.n, opts.fresh),
105
+ };
106
+ }
107
+ throw e;
108
+ }
109
+ case 'news':
110
+ return { endpoint: 'news/search', results: await this.newsSearch(q, opts.n, opts.fresh) };
111
+ case 'images':
112
+ return { endpoint: 'images/search', results: await this.imageSearch(q, opts.n) };
113
+ case 'videos':
114
+ return { endpoint: 'videos/search', results: await this.videoSearch(q, opts.n, opts.fresh) };
115
+ case 'links':
116
+ default:
117
+ return { endpoint: 'web/search', results: await this.webSearch(q, opts.n, opts.fresh) };
118
+ }
119
+ }
120
+
121
+
122
+ private async webSearch(q: string, n: number, fresh: Freshness): Promise<BraveResult[]> {
123
+ const p = this.commonParams(q, n, fresh);
124
+ p.set('text_decorations', 'false');
125
+ p.set('spellcheck', 'true');
126
+ const data = await this.get<WebSearchResponse>('web/search', p);
127
+ const items = data.web?.results ?? [];
128
+ return items.map((r) => ({ title: cleanText(r.title), url: String(r.url ?? ''), snippet: cleanText(r.description) }));
129
+ }
130
+
131
+ private async newsSearch(q: string, n: number, fresh: Freshness): Promise<BraveResult[]> {
132
+ const p = this.commonParams(q, n, fresh);
133
+ p.set('spellcheck', 'true');
134
+ const data = await this.get<NewsSearchResponse>('news/search', p);
135
+ const items = data.results ?? [];
136
+ return items.map((r) => ({ title: cleanText(r.title), url: String(r.url ?? ''), snippet: cleanText(r.description) }));
137
+ }
138
+
139
+ private async videoSearch(q: string, n: number, fresh: Freshness): Promise<BraveResult[]> {
140
+ const p = this.commonParams(q, n, fresh);
141
+ p.set('spellcheck', 'true');
142
+ const data = await this.get<VideoSearchResponse>('videos/search', p);
143
+ const items = data.results ?? [];
144
+ return items.map((r) => {
145
+ const extra = [r.video?.duration, r.video?.publisher].filter(Boolean).join(' · ');
146
+ const desc = cleanText(r.description);
147
+ return {
148
+ title: cleanText(r.title),
149
+ url: String(r.url ?? ''),
150
+ snippet: [desc, extra].filter(Boolean).join(' — '),
151
+ };
152
+ });
153
+ }
154
+
155
+ private async imageSearch(q: string, n: number): Promise<BraveResult[]> {
156
+ // Image Search 将 safesearch 归一为 off 或 strict,不发送 moderate。
157
+ const p = new URLSearchParams();
158
+ p.set('q', q);
159
+ p.set('country', this.cfg.country);
160
+ p.set('search_lang', this.cfg.searchLang);
161
+ p.set('count', String(n));
162
+ p.set('safesearch', this.cfg.safesearch === 'off' ? 'off' : 'strict');
163
+ p.set('spellcheck', 'true');
164
+ const data = await this.get<ImageSearchResponse>('images/search', p);
165
+ const items = data.results ?? [];
166
+ return items.map((r) => {
167
+ const snippet = [
168
+ r.source ? `source: ${r.source}` : '',
169
+ r.properties?.url ? `image: ${r.properties.url}` : '',
170
+ ].filter(Boolean).join(' · ');
171
+ return { title: cleanText(r.title), url: String(r.url ?? ''), snippet };
172
+ });
173
+ }
174
+
175
+ /** 为 LLM 提供 grounding 上下文,通过结果数、token 与 URL 上限约束响应规模。 */
176
+ private async llmContext(q: string, n: number, fresh: Freshness): Promise<BraveResult[]> {
177
+ const p = new URLSearchParams();
178
+ p.set('q', q);
179
+ p.set('country', this.cfg.country);
180
+ p.set('search_lang', this.cfg.searchLang);
181
+ p.set('count', '8');
182
+ p.set('maximum_number_of_urls', String(Math.min(Math.max(n, 1), 5)));
183
+ p.set('maximum_number_of_tokens', '2048');
184
+ p.set('context_threshold_mode', 'balanced');
185
+ const fr = FRESH_MAP[fresh];
186
+ if (fr) p.set('freshness', fr);
187
+ const data = await this.get<LlmContextResponse>('llm/context', p);
188
+ const generic = data.grounding?.generic ?? [];
189
+ const sources = data.sources ?? {};
190
+ return generic.slice(0, n).map((g) => {
191
+ const url = String(g.url ?? '');
192
+ const title = cleanText(g.title || sources[url]?.title || url, 120);
193
+ const snippet = cleanText((g.snippets ?? []).slice(0, 3).join(' … '), 260);
194
+ return { title, url, snippet };
195
+ });
196
+ }
197
+
198
+
199
+ private commonParams(q: string, n: number, fresh: Freshness): URLSearchParams {
200
+ const p = new URLSearchParams();
201
+ p.set('q', q);
202
+ p.set('country', this.cfg.country);
203
+ p.set('search_lang', this.cfg.searchLang);
204
+ p.set('ui_lang', this.cfg.uiLang);
205
+ p.set('count', String(n));
206
+ p.set('safesearch', this.cfg.safesearch);
207
+ const fr = FRESH_MAP[fresh];
208
+ if (fr) p.set('freshness', fr);
209
+ return p;
210
+ }
211
+
212
+ /** GET + 鉴权头 + 超时 + 429/5xx/超时退避重试;其余错误(含OPTION_NOT_IN_PLAN)直抛 */
213
+ private async get<T>(path: string, params: URLSearchParams): Promise<T> {
214
+ const url = `${BASE_URL}/${path}?${params.toString()}`;
215
+ let lastErr: unknown;
216
+ for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) {
217
+ if (attempt > 0) {
218
+ const delay = RETRY_DELAYS_MS[attempt - 1];
219
+ this.log.warn(`Brave请求重试 #${attempt},等待${delay}ms`, { path, err: String(lastErr) });
220
+ await new Promise((r) => setTimeout(r, delay));
221
+ }
222
+ const ac = new AbortController();
223
+ const timer = setTimeout(() => ac.abort(), this.cfg.timeoutMs);
224
+ try {
225
+ const res = await fetch(url, {
226
+ headers: {
227
+ Accept: 'application/json',
228
+ 'Accept-Encoding': 'gzip',
229
+ 'X-Subscription-Token': this.cfg.apiKey,
230
+ },
231
+ signal: ac.signal,
232
+ });
233
+ const text = await res.text();
234
+ if (!res.ok) {
235
+ let code: string | undefined;
236
+ try {
237
+ code = (JSON.parse(text) as { error?: { code?: string } })?.error?.code;
238
+ } catch {
239
+ /* 坏JSON body,code留空 */
240
+ }
241
+ const err = new BraveApiError(`Brave API ${res.status}${code ? ` (${code})` : ''}`, res.status, code);
242
+ if (res.status === 429 || res.status >= 500) {
243
+ lastErr = err;
244
+ continue;
245
+ }
246
+ throw err;
247
+ }
248
+ return JSON.parse(text) as T;
249
+ } catch (e) {
250
+ if (e instanceof BraveApiError && e.status !== 429 && e.status < 500) throw e;
251
+ // 网络错误/超时(AbortError)/可重试的BraveApiError
252
+ lastErr = e;
253
+ continue;
254
+ } finally {
255
+ clearTimeout(timer);
256
+ }
257
+ }
258
+ if (lastErr instanceof BraveApiError) throw lastErr;
259
+ throw new BraveApiError(`Brave请求失败(重试${RETRY_DELAYS_MS.length}次后放弃): ${String(lastErr)}`, 0);
260
+ }
261
+ }
@@ -0,0 +1,75 @@
1
+ import type { ConfigGroup } from '../../core/types.ts';
2
+ import type { BraveClientConfig } from './brave-client.ts';
3
+
4
+ /** World 自有配置的默认值;启用状态由部署装配配置决定。 */
5
+ export const WEBSEARCH_DEFAULTS = {
6
+ enabled: false,
7
+ /** 结果地区(ISO 2位);中文场景默认CN */
8
+ country: 'CN',
9
+ searchLang: 'zh-hans',
10
+ uiLang: 'zh-CN',
11
+ safesearch: 'moderate' as BraveClientConfig['safesearch'],
12
+ timeoutMs: 20000,
13
+ } as const;
14
+
15
+ /** 本 World 需要的密钥的环境变量名(core 不认识这个名字) */
16
+ export const WEBSEARCH_SECRET = 'BRAVE_API_KEY';
17
+
18
+ /** 本 World 声明的可调项:搜哪个地区、用什么语言、多久超时 */
19
+ export const WEBSEARCH_CONFIG_GROUP: ConfigGroup = {
20
+ id: 'world:websearch',
21
+ owner: 'world:websearch',
22
+ schema: {
23
+ type: 'object',
24
+ title: 'WebSearch · 检索偏好',
25
+ description: 'Brave Search 的地区与语言;改完重启生效(客户端构造时固定)。',
26
+ properties: {
27
+ 'worlds.websearch.country': {
28
+ type: 'string',
29
+ title: '结果地区',
30
+ 'x-hot': false,
31
+ description: 'ISO 两位国家码,如 CN / US。',
32
+ },
33
+ 'worlds.websearch.searchLang': {
34
+ type: 'string',
35
+ title: '搜索语言',
36
+ 'x-hot': false,
37
+ description: '如 zh-hans。',
38
+ },
39
+ 'worlds.websearch.uiLang': {
40
+ type: 'string',
41
+ title: '响应语言',
42
+ 'x-hot': false,
43
+ description: '如 zh-CN。',
44
+ },
45
+ 'worlds.websearch.safesearch': {
46
+ type: 'string',
47
+ title: '安全搜索',
48
+ enum: ['off', 'moderate', 'strict'],
49
+ 'x-hot': false,
50
+ description: '过滤强度。',
51
+ },
52
+ 'worlds.websearch.timeoutMs': {
53
+ type: 'integer',
54
+ title: '请求超时',
55
+ minimum: 1000,
56
+ maximum: 120_000,
57
+ multipleOf: 1000,
58
+ 'x-scale': 1000,
59
+ 'x-suffix': 's',
60
+ 'x-hot': false,
61
+ description: '单次搜索请求的超时。',
62
+ },
63
+ },
64
+ },
65
+ };
66
+
67
+ /** config.json 的 `worlds.websearch` 节。全部在构造时读走,改完重启 World 生效。 */
68
+ export interface WebSearchConfigSection {
69
+ enabled: boolean;
70
+ country: string;
71
+ searchLang: string;
72
+ uiLang: string;
73
+ safesearch: BraveClientConfig['safesearch'];
74
+ timeoutMs: number;
75
+ }
@@ -0,0 +1,20 @@
1
+ import type { WorldDefinition } from '../../world.ts';
2
+ import type { BraveClientConfig } from './brave-client.ts';
3
+ import { type WebSearchConfigSection, WEBSEARCH_DEFAULTS, WEBSEARCH_SECRET } from './config.ts';
4
+ import { WebSearchWorld } from './world.ts';
5
+
6
+
7
+ export const WEBSEARCH: WorldDefinition<WebSearchConfigSection> = {
8
+ id: 'websearch',
9
+ label: 'WebSearch',
10
+ defaults: () => ({ ...WEBSEARCH_DEFAULTS }),
11
+ create: (ctx) =>
12
+ new WebSearchWorld({
13
+ apiKey: ctx.secret(WEBSEARCH_SECRET),
14
+ country: ctx.cfg.country,
15
+ searchLang: ctx.cfg.searchLang,
16
+ uiLang: ctx.cfg.uiLang,
17
+ safesearch: ctx.cfg.safesearch,
18
+ timeoutMs: ctx.cfg.timeoutMs,
19
+ }),
20
+ };
@@ -0,0 +1,171 @@
1
+ /**
2
+ * WebSearch 是仅提供请求/响应工具的 World。
3
+ * 它不提供环境提示词或事件;搜索结果不落事件库且不参与异步投递。
4
+ */
5
+ import { readFileSync } from 'node:fs';
6
+ import { fileURLToPath } from 'node:url';
7
+ import type { ConfigGroup, World, WorldHost, WorldConsoleDecl, ToolDef } from '../../core/types.ts';
8
+ import {
9
+ BraveClient,
10
+ type BraveClientConfig,
11
+ type Freshness,
12
+ type SearchMode,
13
+ } from './brave-client.ts';
14
+ import { WEBSEARCH_DEFAULTS, WEBSEARCH_SECRET, WEBSEARCH_CONFIG_GROUP } from './config.ts';
15
+
16
+ const ENV_PROMPT_FILE = fileURLToPath(new URL('./ENV_PROMPT.md', import.meta.url));
17
+
18
+
19
+
20
+
21
+ interface WebSearchWorldConfig {
22
+ /** Brave Search API key(装配层按 WEBSEARCH_SECRET 取给它);空串=工具存在但每次调用都提示未配置 */
23
+ apiKey: string;
24
+ country?: string;
25
+ searchLang?: string;
26
+ uiLang?: string;
27
+ safesearch?: BraveClientConfig['safesearch'];
28
+ timeoutMs?: number;
29
+ }
30
+
31
+ const MODES: SearchMode[] = ['auto', 'answer', 'links', 'news', 'images', 'videos'];
32
+ const FRESHNESS: Freshness[] = ['any', 'day', 'week', 'month', 'year'];
33
+ const N_MIN = 1;
34
+ const N_MAX = 6;
35
+ const N_DEFAULT = 4;
36
+ const Q_MIN = 1;
37
+ const Q_MAX = 240;
38
+
39
+ /** 每次有结果都附上:外部平台有自己的内容规则,照搬检索结果可能导致账号被封 */
40
+ const PLATFORM_RISK_WARNING =
41
+ '⚠ Before posting any of this into QQ or other external platforms: make sure it doesn\'t violate that platform\'s rules (illegal, explicit, violent, infringing, etc.) — sharing content that does can get the account banned. Paraphrase and use judgment.';
42
+
43
+ export class WebSearchWorld implements World {
44
+ readonly id = 'websearch';
45
+
46
+ private readonly client: BraveClient | null;
47
+
48
+ constructor(cfg: WebSearchWorldConfig) {
49
+ this.client = cfg.apiKey
50
+ ? new BraveClient({
51
+ apiKey: cfg.apiKey,
52
+ country: cfg.country ?? 'CN',
53
+ searchLang: cfg.searchLang ?? 'zh-hans',
54
+ uiLang: cfg.uiLang ?? 'zh-CN',
55
+ safesearch: cfg.safesearch ?? 'moderate',
56
+ timeoutMs: cfg.timeoutMs ?? 20000,
57
+ })
58
+ : null;
59
+ }
60
+
61
+ /** 变量表为空;模板留空时,该 World 不进入前缀。 */
62
+ envPromptVars(): Record<string, string> {
63
+ return {};
64
+ }
65
+
66
+ /** 控制台里露出什么:密钥有没有配好 + 自己的检索偏好旋钮 */
67
+ console(): WorldConsoleDecl {
68
+ return {
69
+ // 一条链路:密钥。没配 = 灰(部署没填,不是运行出错);工具照旧在,调用会自己说明。
70
+ lamps: [this.client
71
+ ? { label: '密钥', state: 'online' as const, hint: '已配置' }
72
+ : { label: '密钥', state: 'offline' as const, hint: '未配置' }],
73
+ badges: [
74
+ this.client
75
+ ? { label: '密钥', value: '已配置', tone: 'on' as const }
76
+ : { label: '密钥', value: '未配置(工具仍在,调用会提示)', tone: 'off' as const },
77
+ ],
78
+ config: [WEBSEARCH_CONFIG_GROUP],
79
+ promptDocs: [
80
+ {
81
+ key: 'worlds.websearch.envPrompt',
82
+ title: 'WebSearch · 环境提示词',
83
+ description: '搜索工具 World 的可选常驻环境;留空时不进入系统前缀。',
84
+ path: ENV_PROMPT_FILE,
85
+ role: 'envPrompt',
86
+ },
87
+ ],
88
+ };
89
+ }
90
+
91
+ async start(_host: WorldHost): Promise<void> {
92
+ }
93
+
94
+ async stop(): Promise<void> {
95
+ }
96
+
97
+ tools(): ToolDef[] {
98
+ return [this.webSearchTool()];
99
+ }
100
+
101
+ private webSearchTool(): ToolDef {
102
+ return {
103
+ name: 'web_search',
104
+ description:
105
+ 'Search the public internet for recent or uncertain information. Returns concise, numbered evidence with sources. Default to calling it once.',
106
+ tags: ['read'],
107
+ parameters: {
108
+ type: 'object',
109
+ additionalProperties: false,
110
+ properties: {
111
+ q: {
112
+ type: 'string',
113
+ minLength: Q_MIN,
114
+ maxLength: Q_MAX,
115
+ description:
116
+ 'Focused search query. Keep key names, dates and platforms; do not paste whole chat logs or private info.',
117
+ },
118
+ mode: {
119
+ type: 'string',
120
+ enum: MODES,
121
+ default: 'auto',
122
+ description:
123
+ '"answer" for evidence to answer a question; "links" for web pages; others search that content type.',
124
+ },
125
+ fresh: {
126
+ type: 'string',
127
+ enum: FRESHNESS,
128
+ default: 'any',
129
+ },
130
+ n: {
131
+ type: 'integer',
132
+ minimum: N_MIN,
133
+ maximum: N_MAX,
134
+ default: N_DEFAULT,
135
+ },
136
+ },
137
+ required: ['q'],
138
+ },
139
+ handler: async (args) => {
140
+ const client = this.client;
141
+ if (!client) return '[tool failed] web search is not configured (missing BRAVE_API_KEY)';
142
+
143
+ const q = typeof args.q === 'string' ? args.q.trim() : '';
144
+ if (q.length < Q_MIN) return '[bad input] q must not be empty';
145
+
146
+ const mode: SearchMode = MODES.includes(args.mode as SearchMode) ? (args.mode as SearchMode) : 'auto';
147
+ const fresh: Freshness = FRESHNESS.includes(args.fresh as Freshness) ? (args.fresh as Freshness) : 'any';
148
+ const n =
149
+ args.n !== undefined && args.n !== null
150
+ ? Math.min(N_MAX, Math.max(N_MIN, Math.trunc(Number(args.n)) || N_DEFAULT))
151
+ : N_DEFAULT;
152
+
153
+ try {
154
+ const { endpoint, results } = await client.search(q.slice(0, Q_MAX), { mode, fresh, n });
155
+ if (results.length === 0) return `(no results for "${q}")`;
156
+
157
+ const lines = [`web_search: "${q}" · ${endpoint} · ${results.length} result(s)`, ''];
158
+ results.forEach((r, i) => {
159
+ lines.push(`${i + 1}. ${r.title || '(untitled)'}`, ` ${r.url}`);
160
+ if (r.snippet) lines.push(` ${r.snippet}`);
161
+ lines.push('');
162
+ });
163
+ lines.push(PLATFORM_RISK_WARNING);
164
+ return lines.join('\n').trimEnd();
165
+ } catch (e) {
166
+ return `[search failed] ${e instanceof Error ? e.message : String(e)}`;
167
+ }
168
+ },
169
+ };
170
+ }
171
+ }