mirror of
https://github.com/jina-ai/node-DeepResearch.git
synced 2025-12-26 06:28:56 +08:00
Merge branch 'main' of https://github.com/jina-ai/node-DeepResearch
This commit is contained in:
commit
1677bc5298
40
.github/workflows/npm-publish.yml
vendored
Normal file
40
.github/workflows/npm-publish.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: NPM Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run lint and tests
|
||||
env:
|
||||
BRAVE_API_KEY: ${{ secrets.BRAVE_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
- name: Build TypeScript
|
||||
run: npm run build
|
||||
|
||||
- name: Publish to npm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: npm publish --access public
|
||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@ -31,4 +31,5 @@ jobs:
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: npm test
|
||||
|
||||
21
package-lock.json
generated
21
package-lock.json
generated
@ -16,6 +16,7 @@
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"ai": "^4.1.21",
|
||||
"axios": "^1.7.9",
|
||||
"commander": "^13.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"duck-duck-scrape": "^2.2.7",
|
||||
"express": "^4.21.2",
|
||||
@ -24,6 +25,7 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/commander": "^2.12.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
||||
@ -1529,6 +1531,16 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/commander": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.0.tgz",
|
||||
"integrity": "sha512-DDmRkovH7jPjnx7HcbSnqKg2JeNANyxNZeUvB0iE+qKBLN+vzN5iSIwt+J2PFSmBuYEut4mgQvI/fTX9YQH/vw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
|
||||
@ -2595,6 +2607,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
|
||||
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
||||
11
package.json
11
package.json
@ -1,8 +1,15 @@
|
||||
{
|
||||
"name": "node-deepresearch",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"build": "tsc",
|
||||
"dev": "npx ts-node src/agent.ts",
|
||||
"search": "npx ts-node src/test-duck.ts",
|
||||
@ -25,6 +32,7 @@
|
||||
"@types/node-fetch": "^2.6.12",
|
||||
"ai": "^4.1.21",
|
||||
"axios": "^1.7.9",
|
||||
"commander": "^13.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"duck-duck-scrape": "^2.2.7",
|
||||
"express": "^4.21.2",
|
||||
@ -33,6 +41,7 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/commander": "^2.12.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.10",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
||||
|
||||
40
src/__tests__/cli.test.ts
Normal file
40
src/__tests__/cli.test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// Mock environment variables
|
||||
process.env.GEMINI_API_KEY = 'test-key';
|
||||
process.env.JINA_API_KEY = 'test-key';
|
||||
|
||||
jest.mock('../agent', () => ({
|
||||
getResponse: jest.fn().mockResolvedValue({
|
||||
result: {
|
||||
action: 'answer',
|
||||
answer: 'Test answer',
|
||||
references: []
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
describe('CLI', () => {
|
||||
test('shows version', async () => {
|
||||
const { stdout } = await execAsync('ts-node src/cli.ts --version');
|
||||
expect(stdout.trim()).toMatch(/\d+\.\d+\.\d+/);
|
||||
});
|
||||
|
||||
test('shows help', async () => {
|
||||
const { stdout } = await execAsync('ts-node src/cli.ts --help');
|
||||
expect(stdout).toContain('deepresearch');
|
||||
expect(stdout).toContain('AI-powered research assistant');
|
||||
});
|
||||
|
||||
test('handles invalid token budget', async () => {
|
||||
try {
|
||||
await execAsync('ts-node src/cli.ts -t invalid "test query"');
|
||||
fail('Should have thrown');
|
||||
} catch (error) {
|
||||
expect((error as { stderr: string }).stderr).toContain('Invalid token budget: must be a number');
|
||||
}
|
||||
});
|
||||
});
|
||||
48
src/cli.ts
Normal file
48
src/cli.ts
Normal file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env node
|
||||
import { Command } from 'commander';
|
||||
import { getResponse } from './agent';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('deepresearch')
|
||||
.description('AI-powered research assistant that keeps searching until it finds the answer')
|
||||
.version(version)
|
||||
.argument('<query>', 'The research query to investigate')
|
||||
.option('-t, --token-budget <number>', 'Maximum token budget', (val) => {
|
||||
const num = parseInt(val);
|
||||
if (isNaN(num)) throw new Error('Invalid token budget: must be a number');
|
||||
return num;
|
||||
}, 1000000)
|
||||
.option('-m, --max-attempts <number>', 'Maximum bad attempts before giving up', (val) => {
|
||||
const num = parseInt(val);
|
||||
if (isNaN(num)) throw new Error('Invalid max attempts: must be a number');
|
||||
return num;
|
||||
}, 3)
|
||||
.option('-v, --verbose', 'Show detailed progress')
|
||||
.action(async (query: string, options: any) => {
|
||||
try {
|
||||
const { result } = await getResponse(
|
||||
query,
|
||||
parseInt(options.tokenBudget),
|
||||
parseInt(options.maxAttempts)
|
||||
);
|
||||
|
||||
if (result.action === 'answer') {
|
||||
console.log('\nAnswer:', result.answer);
|
||||
if (result.references?.length) {
|
||||
console.log('\nReferences:');
|
||||
result.references.forEach(ref => {
|
||||
console.log(`- ${ref.url}`);
|
||||
console.log(` "${ref.exactQuote}"`);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program.parse();
|
||||
@ -7,6 +7,8 @@
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user