dashscope-search 1.0.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/README.md +44 -0
- package/index.js +102 -0
- package/package.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# dashscope-search
|
|
2
|
+
|
|
3
|
+
DashScope web search plugin for OpenClaw. Uses Qwen OAuth token for free web search.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g dashscope-search
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configure OpenClaw
|
|
12
|
+
|
|
13
|
+
Add to `~/.openclaw/openclaw.json`:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"tools": {
|
|
18
|
+
"web": {
|
|
19
|
+
"search": {
|
|
20
|
+
"provider": "dashscope-search"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"plugins": {
|
|
25
|
+
"entries": {
|
|
26
|
+
"dashscope-search": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"config": {}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## How It Works
|
|
36
|
+
|
|
37
|
+
1. Queries your local qwen-proxy (port 4099) first
|
|
38
|
+
2. Falls back to direct DashScope API using `~/.qwen/oauth_creds.json`
|
|
39
|
+
3. Returns Tavily-compatible format for OpenClaw compatibility
|
|
40
|
+
4. No API keys needed — uses your Qwen OAuth token
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as fs from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
export function registerWebSearchProvider(context) {
|
|
9
|
+
return {
|
|
10
|
+
name: 'dashscope-search',
|
|
11
|
+
|
|
12
|
+
async search(query, options = {}) {
|
|
13
|
+
const maxResults = options.maxResults || 10;
|
|
14
|
+
|
|
15
|
+
// Try our proxy first (uses Qwen OAuth token)
|
|
16
|
+
const proxyResult = await searchViaProxy(query, maxResults);
|
|
17
|
+
if (proxyResult) return proxyResult;
|
|
18
|
+
|
|
19
|
+
// Fallback: call DashScope directly using OAuth creds
|
|
20
|
+
return await searchDirectDashScope(query, maxResults);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// What OpenClaw expects
|
|
24
|
+
getDisplayName() {
|
|
25
|
+
return 'DashScope';
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
isAvailable() {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function searchViaProxy(query, maxResults) {
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch('http://127.0.0.1:4099/v1/search', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
body: JSON.stringify({ query, max_results: maxResults }),
|
|
40
|
+
signal: AbortSignal.timeout(10000)
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!res.ok) return null;
|
|
44
|
+
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
if (!data.results || data.results.length === 0) return null;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
query,
|
|
50
|
+
results: data.results.map(r => ({
|
|
51
|
+
title: r.title || '',
|
|
52
|
+
url: r.url || '',
|
|
53
|
+
content: r.content || '',
|
|
54
|
+
score: r.score || 0,
|
|
55
|
+
publishedDate: r.published_date || ''
|
|
56
|
+
}))
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function searchDirectDashScope(query, maxResults) {
|
|
64
|
+
try {
|
|
65
|
+
const credsPath = path.join(os.homedir(), '.qwen', 'oauth_creds.json');
|
|
66
|
+
const creds = JSON.parse(await fs.readFile(credsPath, 'utf-8'));
|
|
67
|
+
|
|
68
|
+
if (!creds.access_token) return null;
|
|
69
|
+
|
|
70
|
+
const resourceUrl = creds.resource_url || 'portal.qwen.ai';
|
|
71
|
+
const baseUrl = resourceUrl.startsWith('http') ? resourceUrl : `https://${resourceUrl}`;
|
|
72
|
+
const apiEndpoint = `${baseUrl.replace(/\/$/, '')}/api/v1/indices/plugin/web_search`;
|
|
73
|
+
|
|
74
|
+
const res = await fetch(apiEndpoint, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
'Authorization': `Bearer ${creds.access_token}`
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ uq: query, page: 1, rows: maxResults }),
|
|
81
|
+
signal: AbortSignal.timeout(10000)
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!res.ok) return null;
|
|
85
|
+
|
|
86
|
+
const data = await res.json();
|
|
87
|
+
if (data.status !== 0 || !data.data?.docs) return null;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
query,
|
|
91
|
+
results: data.data.docs.map(doc => ({
|
|
92
|
+
title: doc.title || '',
|
|
93
|
+
url: doc.url || '',
|
|
94
|
+
content: doc.snippet || '',
|
|
95
|
+
score: doc._score || 0,
|
|
96
|
+
publishedDate: doc.timestamp_format || ''
|
|
97
|
+
}))
|
|
98
|
+
};
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dashscope-search",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "DashScope web search plugin for OpenClaw using Qwen OAuth",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": ["openclaw", "dashscope", "web-search", "qwen"],
|
|
8
|
+
"author": "wraient",
|
|
9
|
+
"license": "MIT"
|
|
10
|
+
}
|