Skip to content

Commit

Permalink
Merge pull request #23 from dcSpark/nico/fix-text-encoder
Browse files Browse the repository at this point in the history
Nico/fix text encoder
  • Loading branch information
nicarq authored Aug 9, 2024
2 parents 64e5fd4 + 7afbe9a commit 74070c2
Show file tree
Hide file tree
Showing 17 changed files with 998 additions and 28 deletions.
49 changes: 49 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions apps/shinkai-tool-duckduckgo-search/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { TextEncoder as NodeTextEncoder, TextDecoder as NodeTextDecoder } from 'util';
global.TextEncoder = NodeTextEncoder as any;
global.TextDecoder = NodeTextDecoder as any;

import { Tool } from '../src/index';
global.fetch = require('node-fetch');

Expand Down
27 changes: 25 additions & 2 deletions apps/shinkai-tool-duckduckgo-search/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BaseTool, RunResult } from '@shinkai_protocol/shinkai-tools-builder';
import { ToolDefinition } from 'libs/shinkai-tools-builder/src/tool-definition';
import { URL } from 'whatwg-url';

type Config = {};
type Params = {
Expand All @@ -13,6 +14,13 @@ interface SearchResult {
url: string;
}

// Custom function to build query string
function buildQueryString(params: Record<string, string>): string {
return Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
}

export class Tool extends BaseTool<Config, Params, Result> {
definition: ToolDefinition<Config, Params, Result> = {
id: 'shinkai-tool-duckduckgo-search',
Expand Down Expand Up @@ -42,9 +50,13 @@ export class Tool extends BaseTool<Config, Params, Result> {
};

private static async getVQD(keywords: string): Promise<string> {
const body = buildQueryString({ q: keywords });
const response = await fetch('https://duckduckgo.com', {
method: 'POST',
body: new URLSearchParams({ q: keywords }),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body,
});
const text = await response.text();
// console.log('DuckDuckGo response HTML:', text);
Expand Down Expand Up @@ -91,8 +103,11 @@ export class Tool extends BaseTool<Config, Params, Result> {
}

private static async textSearch(keywords: string): Promise<any[]> {
console.log('textSearch: ', keywords);
const vqd = await this.getVQD(keywords);
console.log('vqd: ', vqd);
const url = new URL('https://links.duckduckgo.com/d.js');
console.log('before url.searchParams.append');
url.searchParams.append('q', keywords);
url.searchParams.append('vqd', vqd);
url.searchParams.append('kl', 'wt-wt');
Expand All @@ -102,17 +117,22 @@ export class Tool extends BaseTool<Config, Params, Result> {
url.searchParams.append('df', '');
url.searchParams.append('ex', '-1');

console.log('before urlString');
const urlString = url.toString();
console.log('urlString: ', urlString);

const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
console.log('response: ', response);
const text = await response.text();
console.log('DuckDuckGo search response:', text);

// Parse the response using the custom parser
const results = Tool.parseDuckDuckGoResponse(text);
const results = this.parseDuckDuckGoResponse(text);
if (results.length === 0) {
throw new Error('Failed to extract search results');
}
Expand All @@ -122,8 +142,11 @@ export class Tool extends BaseTool<Config, Params, Result> {

async run(params: Params): Promise<RunResult<Result>> {
console.log('run duckduckgo search from js', 4);
console.log('second message', 4);
console.log('params: ', params);
try {
const results = await Tool.textSearch(params.message);
console.log('results: ', results);
return { data: { message: JSON.stringify(results) } };
} catch (error) {
let errorMessage = 'An unknown error occurred';
Expand Down
10 changes: 9 additions & 1 deletion libs/shinkai-tools-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ futures = "0.3.30"
nanoid = "0.4.0"
patch = "0.7.0"
reqwest = { version = "0.11.26", features = ["blocking"] }
rquickjs = { version = "0.6.2", features = ["full-async", "futures", "macro"] }
hex-simd = "0.8.0"
base64-simd = "0.8.0"
phf = { version = "0.11.2", features = ["macros"] }
rquickjs = { version = "0.6.2", features = [
"full-async",
"futures",
"macro",
"array-buffer",
] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.117"
tokio = { version = "1.36.0", features = ["full"] }
Expand Down
20 changes: 10 additions & 10 deletions libs/shinkai-tools-runner/src/built_in_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,16 @@ lazy_static! {
)),
);
// https://bugzilla.mozilla.org/show_bug.cgi?id=1681809
// m.insert(
// "shinkai-tool-token-price",
// &*Box::leak(Box::new(
// serde_json::from_str::<ToolDefinition>(include_str!(concat!(
// env!("CARGO_MANIFEST_DIR"),
// "/tools/shinkai-tool-token-price/definition.json"
// )))
// .unwrap(),
// )),
// );
m.insert(
"shinkai-tool-token-price",
&*Box::leak(Box::new(
serde_json::from_str::<ToolDefinition>(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tools/shinkai-tool-token-price/definition.json"
)))
.unwrap(),
)),
);
m.insert(
"shinkai-tool-duckduckgo-search",
&*Box::leak(Box::new(
Expand Down
22 changes: 22 additions & 0 deletions libs/shinkai-tools-runner/src/lib.test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,25 @@ async fn shinkai_tool_leiden() {
println!("Execution time: {:?}", elapsed_time); // Print the elapsed time
assert!(run_result.data["bestClustering"]["nClusters"].as_u64().unwrap() > 0);
}

#[tokio::test]
async fn shinkai_tool_duckduckgo_search() {
let tool_definition = get_tool("shinkai-tool-duckduckgo-search").unwrap();
let mut tool = Tool::new();
let _ = tool
.load_from_code(&tool_definition.code.clone().unwrap(), "")
.await;
let run_result = tool
.run("{ \"message\": \"best movie of all time\" }", None)
.await
.unwrap();
eprintln!("result: {:?}", run_result.data);
let message = run_result.data["message"].as_str().unwrap();
let search_results: Vec<serde_json::Value> = serde_json::from_str(message).unwrap();

// assert!(search_results.is_array());
assert!(!search_results.is_empty());
assert!(search_results[0].get("title").is_some());
assert!(search_results[0].get("url").is_some());
assert!(search_results[0].get("description").is_some());
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ use rquickjs::{Ctx, Result};
mod console;
mod fetch;
mod timers;
mod text_encoder;
mod text_decoder;
mod utils;

pub fn init_globals(ctx: &Ctx<'_>) -> Result<()> {
console::init(ctx)?;
fetch::init(ctx)?;
timers::init(ctx)?;
text_encoder::init(ctx)?;
text_decoder::init(ctx)?;
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use rquickjs::{function::Opt, Class, Ctx, Object, Result, Value};

use super::utils::{bytes::get_bytes, encoding::Encoder, object::ObjectExt, result::ResultExt};

#[rquickjs::class]
#[derive(rquickjs::class::Trace)]
pub struct TextDecoder {
#[qjs(skip_trace)]
encoder: Encoder,
fatal: bool,
ignore_bom: bool,
}

#[rquickjs::methods]
impl<'js> TextDecoder {
#[qjs(constructor)]
pub fn new(ctx: Ctx<'js>, label: Opt<String>, options: Opt<Object<'js>>) -> Result<Self> {
let encoding = label
.0
.filter(|lbl| !lbl.is_empty())
.unwrap_or_else(|| String::from("utf-8"));
let mut fatal = false;
let mut ignore_bom = false;

let encoder = Encoder::from_str(&encoding).or_throw_range(&ctx, None)?;

if let Some(options) = options.0 {
if let Some(opt) = options.get_optional("fatal")? {
fatal = opt;
}
if let Some(opt) = options.get_optional("ignoreBOM")? {
ignore_bom = opt;
}
}

Ok(TextDecoder {
encoder,
fatal,
ignore_bom,
})
}

#[qjs(get)]
fn encoding(&self) -> &str {
self.encoder.as_label()
}

#[qjs(get)]
fn fatal(&self) -> bool {
self.fatal
}

#[qjs(get, rename = "ignoreBOM")]
fn ignore_bom(&self) -> bool {
self.ignore_bom
}

pub fn decode(&self, ctx: Ctx<'js>, buffer: Value<'js>) -> Result<String> {
let bytes = get_bytes(&ctx, buffer)?;
let start_pos = if !self.ignore_bom {
match bytes.get(..3) {
Some([0xFF, 0xFE, ..]) | Some([0xFE, 0xFF, ..]) => 2,
Some([0xEF, 0xBB, 0xBF]) => 3,
_ => 0,
}
} else {
0
};

self.encoder
.encode_to_string(&bytes[start_pos..], !self.fatal)
.or_throw_type(&ctx, None)
}
}

pub fn init(ctx: &Ctx<'_>) -> Result<()> {
let globals = ctx.globals();
Class::<TextDecoder>::define(&globals)?;
Ok(())
}
Loading

0 comments on commit 74070c2

Please sign in to comment.