@2en/clawly-plugins 1.30.0-beta.8 → 1.30.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.
@@ -109,7 +109,12 @@ export function initCache(): void {
109
109
  }
110
110
 
111
111
  export function getCache(): CalendarCache | null {
112
- if (!cachedData) cachedData = loadCache()
112
+ // Always read from disk to avoid returning stale in-memory state.
113
+ // After gateway restart or ESM/CJS dual-loading, gateway methods and
114
+ // agent tools may hold separate module instances with divergent
115
+ // cachedData. Disk is the shared source of truth. The file is small
116
+ // (<10 KB) and tool calls are infrequent, so the I/O cost is negligible.
117
+ cachedData = loadCache()
113
118
  return cachedData
114
119
  }
115
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.30.0-beta.8",
3
+ "version": "1.30.0",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,6 +1,11 @@
1
1
  import {afterAll, beforeEach, describe, expect, test} from 'bun:test'
2
2
  import type {PluginApi} from '../types'
3
- import {registerDeepSearchTool, registerGrokSearchTool, registerSearchTool} from './clawly-search'
3
+ import {
4
+ registerDeepSearchTool,
5
+ registerGrokSearchTool,
6
+ registerKimiSearchTool,
7
+ registerSearchTool,
8
+ } from './clawly-search'
4
9
 
5
10
  // ── Helpers ──────────────────────────────────────────────────────
6
11
 
@@ -267,3 +272,45 @@ describe('clawly_grok_search', () => {
267
272
  expect(res.error).toContain('Grok API error 429')
268
273
  })
269
274
  })
275
+
276
+ // ── clawly_kimi_search ──────────────────────────────────────────
277
+
278
+ describe('clawly_kimi_search', () => {
279
+ test('uses Kimi gateway endpoint with web_search tool', async () => {
280
+ mockResponse.body = {
281
+ choices: [{message: {content: 'Kimi answer with [source](https://example.com/page)'}}],
282
+ }
283
+ const {execute} = createMockApi(registerKimiSearchTool)
284
+ const res = parseResult(await execute('tc-1', {query: 'Chinese news'}))
285
+
286
+ expect(fetchCalls).toHaveLength(1)
287
+ const call = fetchCalls[0]
288
+ expect(call.url).toBe('https://gw.example.com/v1/kimi/v1/chat/completions')
289
+ expect(call.body?.model).toBe('kimi-k2-turbo-preview')
290
+ expect(call.body?.stream).toBe(false)
291
+ expect(call.body?.messages).toEqual([{role: 'user', content: 'Chinese news'}])
292
+ expect(call.body?.tools).toEqual([{type: 'builtin_function', function: {name: 'web_search'}}])
293
+
294
+ expect(res.answer).toBe('Kimi answer with [source](https://example.com/page)')
295
+ expect(res.citations).toEqual(['https://example.com/page'])
296
+ expect(res.provider).toBe('kimi')
297
+ })
298
+
299
+ test('returns empty citations when response has no inline links', async () => {
300
+ mockResponse.body = {choices: [{message: {content: 'Plain answer'}}]}
301
+ const {execute} = createMockApi(registerKimiSearchTool)
302
+ const res = parseResult(await execute('tc-1', {query: 'test'}))
303
+
304
+ expect(res.answer).toBe('Plain answer')
305
+ expect(res.citations).toEqual([])
306
+ expect(res.provider).toBe('kimi')
307
+ })
308
+
309
+ test('returns error on Kimi API failure', async () => {
310
+ mockResponse = {ok: false, status: 429, body: {error: 'rate limited'}}
311
+ const {execute} = createMockApi(registerKimiSearchTool)
312
+ const res = parseResult(await execute('tc-1', {query: 'test'}))
313
+
314
+ expect(res.error).toContain('Kimi API error 429')
315
+ })
316
+ })
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  buildGrokBody,
3
3
  buildGrokUrl,
4
+ buildKimiBody,
5
+ buildKimiUrl,
4
6
  buildPerplexityUrl,
5
7
  createSearchToolRegistrar,
6
8
  parseGrokResponse,
9
+ parseKimiResponse,
7
10
  } from './create-search-tool'
8
11
 
9
12
  export const registerSearchTool = createSearchToolRegistrar({
@@ -40,3 +43,14 @@ export const registerGrokSearchTool = createSearchToolRegistrar({
40
43
  buildBody: buildGrokBody,
41
44
  parseResponse: parseGrokResponse,
42
45
  })
46
+
47
+ export const registerKimiSearchTool = createSearchToolRegistrar({
48
+ toolName: 'clawly_kimi_search',
49
+ description: 'Search the web using Kimi. Good for Chinese-language queries and content.',
50
+ model: 'kimi-k2-turbo-preview',
51
+ buildUrl: buildKimiUrl,
52
+ timeoutMs: 30_000,
53
+ provider: 'kimi',
54
+ buildBody: buildKimiBody,
55
+ parseResponse: parseKimiResponse,
56
+ })
@@ -1,7 +1,7 @@
1
1
  import type {PluginApi} from '../types'
2
2
  import {resolveGatewayCredentials} from '../resolve-gateway-credentials'
3
3
 
4
- export type SearchProvider = 'grok' | 'perplexity'
4
+ export type SearchProvider = 'grok' | 'kimi' | 'perplexity'
5
5
 
6
6
  export interface SearchResult {
7
7
  query: string
@@ -44,6 +44,19 @@ export function buildGrokBody(model: string, query: string): Record<string, unkn
44
44
  return {model, input: query, tools: [{type: 'web_search'}]}
45
45
  }
46
46
 
47
+ export function buildKimiUrl(baseUrl: string): string {
48
+ return `${baseUrl.replace(/\/$/, '')}/kimi/v1/chat/completions`
49
+ }
50
+
51
+ export function buildKimiBody(model: string, query: string): Record<string, unknown> {
52
+ return {
53
+ model,
54
+ stream: false,
55
+ messages: [{role: 'user', content: query}],
56
+ tools: [{type: 'builtin_function', function: {name: 'web_search'}}],
57
+ }
58
+ }
59
+
47
60
  function extractCitations(text: string): string[] {
48
61
  const urls: string[] = []
49
62
  const seen = new Set<string>()
@@ -57,6 +70,12 @@ function extractCitations(text: string): string[] {
57
70
  return urls
58
71
  }
59
72
 
73
+ export function parseKimiResponse(data: unknown): {answer: string; citations: string[]} {
74
+ const d = data as {choices?: {message?: {content?: string}}[]}
75
+ const answerText = d.choices?.[0]?.message?.content ?? ''
76
+ return {answer: answerText, citations: extractCitations(answerText)}
77
+ }
78
+
60
79
  export function parseGrokResponse(data: unknown): {answer: string; citations: string[]} {
61
80
  const typed = data as {
62
81
  output?: Array<{type: string; content?: Array<{type: string; text?: string}>}>
@@ -113,9 +132,13 @@ export function createSearchToolRegistrar(config: SearchToolConfig) {
113
132
 
114
133
  if (!res.ok) {
115
134
  const body = await res.text().catch(() => '')
116
- throw new Error(
117
- `${config.provider === 'grok' ? 'Grok' : 'Perplexity'} API error ${res.status}: ${body.slice(0, 200)}`,
118
- )
135
+ const providerLabel =
136
+ config.provider === 'grok'
137
+ ? 'Grok'
138
+ : config.provider === 'kimi'
139
+ ? 'Kimi'
140
+ : 'Perplexity'
141
+ throw new Error(`${providerLabel} API error ${res.status}: ${body.slice(0, 200)}`)
119
142
  }
120
143
 
121
144
  const data = await res.json()
package/tools/index.ts CHANGED
@@ -2,7 +2,12 @@ import type {PluginApi} from '../types'
2
2
  import {registerCalendarTools} from './clawly-calendar'
3
3
  import {registerIsUserOnlineTool} from './clawly-is-user-online'
4
4
  import {registerMsgBreakTool} from './clawly-msg-break'
5
- import {registerDeepSearchTool, registerGrokSearchTool, registerSearchTool} from './clawly-search'
5
+ import {
6
+ registerDeepSearchTool,
7
+ registerGrokSearchTool,
8
+ registerKimiSearchTool,
9
+ registerSearchTool,
10
+ } from './clawly-search'
6
11
  import {registerSendAppPushTool} from './clawly-send-app-push'
7
12
  import {registerSendFileTool} from './clawly-send-file'
8
13
  import {registerSendMessageTool} from './clawly-send-message'
@@ -14,6 +19,7 @@ export function registerTools(api: PluginApi) {
14
19
  registerSearchTool(api)
15
20
  registerDeepSearchTool(api)
16
21
  registerGrokSearchTool(api)
22
+ registerKimiSearchTool(api)
17
23
  registerSendAppPushTool(api)
18
24
  registerSendFileTool(api)
19
25
  registerSendMessageTool(api)