@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.
- package/lib/calendar-cache.ts +6 -1
- package/package.json +1 -1
- package/tools/clawly-search.test.ts +48 -1
- package/tools/clawly-search.ts +14 -0
- package/tools/create-search-tool.ts +27 -4
- package/tools/index.ts +7 -1
package/lib/calendar-cache.ts
CHANGED
|
@@ -109,7 +109,12 @@ export function initCache(): void {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
export function getCache(): CalendarCache | null {
|
|
112
|
-
|
|
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,11 @@
|
|
|
1
1
|
import {afterAll, beforeEach, describe, expect, test} from 'bun:test'
|
|
2
2
|
import type {PluginApi} from '../types'
|
|
3
|
-
import {
|
|
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
|
+
})
|
package/tools/clawly-search.ts
CHANGED
|
@@ -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
|
-
|
|
117
|
-
|
|
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 {
|
|
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)
|