From 66d3c0623088f7dd2fce6b18ec30742670c6a8af Mon Sep 17 00:00:00 2001 From: geoffsee <> Date: Fri, 23 May 2025 09:48:26 -0400 Subject: [PATCH] init --- .dockerignore | 10 + .env | 12 + .env.cerebras | 4 + .env.fireworks | 4 + .env.google | 4 + .env.groq | 4 + .env.openai | 4 + .gitignore | 12 + .toak-ignore | 3 + Cargo.lock | 1391 ++++++++++++++ Cargo.toml | 33 + Dockerfile | 88 + Local.Dockerfile | 88 + README.md | 13 + Remote.Dockerfile | 88 + assets/index.html | 62 + bun.lockb | Bin 0 -> 562032 bytes compose.yml | 32 + docs/tokens.md | 77 + fly.toml | 54 + gitleaks-report.json | 1 + package.json | 20 + packages/core/index.ts | 4 + packages/core/market/index.ts | 14 + packages/core/market/types.ts | 48 + packages/core/news/index.ts | 178 ++ packages/core/news/news.test.ts | 75 + packages/core/package.json | 9 + packages/core/portfolio/index.ts | 137 ++ packages/core/quotes/index.ts | 13 + packages/core/quotes/models.ts | 77 + packages/core/quotes/quote.test.ts | 17 + packages/core/quotes/types.ts | 71 + packages/core/types/index.ts | 69 + packages/genaiscript-rust-shim/.gitignore | 175 ++ packages/genaiscript-rust-shim/README.md | 15 + packages/genaiscript-rust-shim/bun.lockb | Bin 0 -> 3144 bytes .../genaiscript-rust-shim.ts | 61 + packages/genaiscript-rust-shim/index.ts | 2 + packages/genaiscript-rust-shim/package.json | 19 + packages/genaiscript-rust-shim/shim-types.ts | 70 + packages/genaiscript-rust-shim/tsconfig.json | 27 + packages/genaiscript/genaisrc/.gitignore | 4 + packages/genaiscript/genaisrc/_state/index.ts | 21 + packages/genaiscript/genaisrc/_state/news.ts | 11 + .../genaiscript/genaisrc/_state/quotes.ts | 7 + packages/genaiscript/genaisrc/agent.genai.mts | 24 + .../genaisrc/deep-research.genai.mts | 281 +++ .../genaisrc/finance-query.genai.mts | 80 + .../genaisrc/image-generator.genai.mts | 11 + .../genaisrc/incubator/firecrawl.genai.mts | 26 + .../genaisrc/news-search.genai.mts | 24 + .../genaisrc/tools/searxng.genai.mts | 18 + .../genaiscript/genaisrc/web-scrape.genai.mts | 88 + .../genaiscript/genaisrc/web-search.genai.mts | 28 + packages/genaiscript/package.json | 28 + packages/perigon/index.ts | 106 ++ packages/perigon/openapi.json | 1629 +++++++++++++++++ packages/perigon/package.json | 12 + packages/perigon/schemas.ts | 5 + packages/perigon/types.ts | 13 + searxng_tester.mts | 29 + src/agents/crypto_market.rs | 28 + src/agents/image_generator.rs | 10 + src/agents/mod.rs | 5 + src/agents/news.rs | 6 + src/agents/scrape.rs | 6 + src/agents/search.rs | 28 + src/config.rs | 30 + src/genaiscript.rs | 90 + src/handlers/error.rs | 17 + src/handlers/mod.rs | 7 + src/handlers/status.rs | 5 + src/handlers/stream.rs | 82 + src/handlers/ui.rs | 34 + src/handlers/webhooks.rs | 261 +++ src/main.rs | 43 + src/routes.rs | 105 ++ src/session_identify.rs | 55 + src/setup.rs | 10 + src/utils/base64.rs | 65 + src/utils/mod.rs | 2 + src/utils/utils.rs | 80 + tsconfig.json | 30 + 84 files changed, 6529 insertions(+) create mode 100644 .dockerignore create mode 100644 .env create mode 100644 .env.cerebras create mode 100644 .env.fireworks create mode 100644 .env.google create mode 100644 .env.groq create mode 100644 .env.openai create mode 100644 .gitignore create mode 100644 .toak-ignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 Local.Dockerfile create mode 100644 README.md create mode 100644 Remote.Dockerfile create mode 100644 assets/index.html create mode 100755 bun.lockb create mode 100644 compose.yml create mode 100644 docs/tokens.md create mode 100644 fly.toml create mode 100644 gitleaks-report.json create mode 100644 package.json create mode 100644 packages/core/index.ts create mode 100644 packages/core/market/index.ts create mode 100644 packages/core/market/types.ts create mode 100644 packages/core/news/index.ts create mode 100644 packages/core/news/news.test.ts create mode 100644 packages/core/package.json create mode 100644 packages/core/portfolio/index.ts create mode 100644 packages/core/quotes/index.ts create mode 100644 packages/core/quotes/models.ts create mode 100644 packages/core/quotes/quote.test.ts create mode 100644 packages/core/quotes/types.ts create mode 100644 packages/core/types/index.ts create mode 100644 packages/genaiscript-rust-shim/.gitignore create mode 100644 packages/genaiscript-rust-shim/README.md create mode 100755 packages/genaiscript-rust-shim/bun.lockb create mode 100644 packages/genaiscript-rust-shim/genaiscript-rust-shim.ts create mode 100644 packages/genaiscript-rust-shim/index.ts create mode 100644 packages/genaiscript-rust-shim/package.json create mode 100644 packages/genaiscript-rust-shim/shim-types.ts create mode 100644 packages/genaiscript-rust-shim/tsconfig.json create mode 100644 packages/genaiscript/genaisrc/.gitignore create mode 100644 packages/genaiscript/genaisrc/_state/index.ts create mode 100644 packages/genaiscript/genaisrc/_state/news.ts create mode 100644 packages/genaiscript/genaisrc/_state/quotes.ts create mode 100644 packages/genaiscript/genaisrc/agent.genai.mts create mode 100644 packages/genaiscript/genaisrc/deep-research.genai.mts create mode 100644 packages/genaiscript/genaisrc/finance-query.genai.mts create mode 100644 packages/genaiscript/genaisrc/image-generator.genai.mts create mode 100644 packages/genaiscript/genaisrc/incubator/firecrawl.genai.mts create mode 100644 packages/genaiscript/genaisrc/news-search.genai.mts create mode 100644 packages/genaiscript/genaisrc/tools/searxng.genai.mts create mode 100644 packages/genaiscript/genaisrc/web-scrape.genai.mts create mode 100644 packages/genaiscript/genaisrc/web-search.genai.mts create mode 100644 packages/genaiscript/package.json create mode 100644 packages/perigon/index.ts create mode 100644 packages/perigon/openapi.json create mode 100644 packages/perigon/package.json create mode 100644 packages/perigon/schemas.ts create mode 100644 packages/perigon/types.ts create mode 100644 searxng_tester.mts create mode 100644 src/agents/crypto_market.rs create mode 100644 src/agents/image_generator.rs create mode 100644 src/agents/mod.rs create mode 100644 src/agents/news.rs create mode 100644 src/agents/scrape.rs create mode 100644 src/agents/search.rs create mode 100644 src/config.rs create mode 100644 src/genaiscript.rs create mode 100644 src/handlers/error.rs create mode 100644 src/handlers/mod.rs create mode 100644 src/handlers/status.rs create mode 100644 src/handlers/stream.rs create mode 100644 src/handlers/ui.rs create mode 100644 src/handlers/webhooks.rs create mode 100644 src/main.rs create mode 100644 src/routes.rs create mode 100644 src/session_identify.rs create mode 100644 src/setup.rs create mode 100644 src/utils/base64.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/utils.rs create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5345600 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +* +!packages/genaiscript/genaisrc +!src +!Cargo.lock +!Cargo.toml +!package.json +!package-lock.json +!assets +!bun.lockb +!packages diff --git a/.env b/.env new file mode 100644 index 0000000..55d33a4 --- /dev/null +++ b/.env @@ -0,0 +1,12 @@ +OPENAI_API_KEY="" +OPENAI_API_BASE="https://api.openai.com/v1" +GENAISCRIPT_MODEL_LARGE="" +GENAISCRIPT_MODEL_SMALL="" +SEARXNG_API_BASE_URL="" +SEARXNG_PASSWORD= + +BING_SEARCH_API_KEY= +PERIGON_API_KEY= +TAVILY_API_KEY= +CCC_API_KEY= +HF_API_KEY= \ No newline at end of file diff --git a/.env.cerebras b/.env.cerebras new file mode 100644 index 0000000..706505e --- /dev/null +++ b/.env.cerebras @@ -0,0 +1,4 @@ +OPENAI_API_KEY="" +OPENAI_API_BASE="https://api.cerebras.ai/v1" +GENAISCRIPT_MODEL_LARGE="" +GENAISCRIPT_MODEL_SMALL="" \ No newline at end of file diff --git a/.env.fireworks b/.env.fireworks new file mode 100644 index 0000000..7d992a3 --- /dev/null +++ b/.env.fireworks @@ -0,0 +1,4 @@ +OPENAI_API_KEY="" +OPENAI_API_BASE="https://api.fireworks.ai/inference/v1" +GENAISCRIPT_MODEL_LARGE="" +GENAISCRIPT_MODEL_SMALL="" \ No newline at end of file diff --git a/.env.google b/.env.google new file mode 100644 index 0000000..72e3eeb --- /dev/null +++ b/.env.google @@ -0,0 +1,4 @@ +OPENAI_API_KEY="" +OPENAI_API_BASE="https://generativelanguage.googleapis.com/v1beta/openai" +GENAISCRIPT_MODEL_LARGE="" +GENAISCRIPT_MODEL_SMALL="" diff --git a/.env.groq b/.env.groq new file mode 100644 index 0000000..f7657e5 --- /dev/null +++ b/.env.groq @@ -0,0 +1,4 @@ +OPENAI_API_KEY="" +OPENAI_API_BASE="https://api.groq.com/openai/v1" +GENAISCRIPT_MODEL_LARGE="" +GENAISCRIPT_MODEL_SMALL="" \ No newline at end of file diff --git a/.env.openai b/.env.openai new file mode 100644 index 0000000..87c506f --- /dev/null +++ b/.env.openai @@ -0,0 +1,4 @@ +OPENAI_API_KEY="" +OPENAI_API_BASE="https://api.openai.com/v1" +GENAISCRIPT_MODEL_LARGE="gpt-4o" +GENAISCRIPT_MODEL_SMALL="gpt-4o-mini" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdcc4c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/target +/node_modules/ +/.idea +prompt.md +todo +chrome +stream_store +data/ +web-agent-rs/ +./.env* +/.genaiscript/ +/packages/genaiscript/node_modules/ diff --git a/.toak-ignore b/.toak-ignore new file mode 100644 index 0000000..e70f0d6 --- /dev/null +++ b/.toak-ignore @@ -0,0 +1,3 @@ +package.json +genaisrc/genaiscript.d.ts +.toak-ignore \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f208495 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1391 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "multer", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fips204" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9fb5a367b9846933e271a3c2a992930743f82ae5e8cb7faa780715a80fa0b15" +dependencies = [ + "rand_core", + "sha2", + "sha3", + "zeroize", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.166" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.7", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot 0.12.3", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http", + "http-body", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "web-agent-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "base64", + "bytes", + "dotenv", + "fips204", + "futures", + "http", + "lazy_static", + "rust-embed", + "serde", + "serde_json", + "shell-escape", + "sled", + "tokio", + "tokio-stream", + "tokio-util", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ddbb0e5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "web-agent-rs" +version = "0.1.0" +edition = "2021" + +[[bin]] +edition = "2021" +name = "agent-server" +path = "src/main.rs" + +[dependencies] +axum = { version = "0.7", features = ["multipart"] } +serde = { version = "1.0", features = ["derive"] } +tokio = { version = "1.0", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +http = "1.1.0" +tokio-stream = "0.1.16" +uuid = { version = "1.11.0", features = ["v4"] } +tokio-util = { version = "0.7", features = ["io"] } +serde_json = "1.0.133" +futures = "0.3.31" +dotenv = "0.15.0" +shell-escape = "0.1.5" +rust-embed = "8.5.0" +bytes = "1.8.0" +lazy_static = "1.5.0" +sled = "0.34.7" +tower-http = { version = "0.6.2", features = ["trace"] } +anyhow = "1.0.97" +base64 = "0.22.1" +fips204 = "0.4.6" + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ef133fc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,88 @@ +# Stage 1: Build the Rust agent-core binary +FROM rust:1.73-slim-bullseye as agent-server-builder + +WORKDIR /build-context + +# Install only the minimal required build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config \ + musl-tools \ + && rm -rf /var/core/apt/lists/* + +# Build for musl to ensure static linking +RUN rustup target add aarch64-unknown-linux-musl + +# Copy only necessary files for building +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY assets ./assets + +# Build with musl target for static linking +RUN cargo build --release --target aarch64-unknown-linux-musl && \ + strip /build-context/target/aarch64-unknown-linux-musl/release/agent-core + +# Stage 2: Build Bun dependencies +FROM oven/bun:alpine as node-builder + +# Install system dependencies and node-gyp +RUN apk add --no-cache \ + ca-certificates \ + curl \ + unzip \ + git \ + python3 \ + py3-pip \ + build-base \ + pkgconf \ + cmake \ + nodejs \ + npm \ + bash \ + chromium \ + nss \ + freetype \ + freetype-dev \ + harfbuzz \ + ca-certificates \ + ttf-freefont \ + font-noto-emoji \ + nodejs \ + wqy-zenhei \ + && rm -rf /var/cache/* \ + && mkdir /var/cache/apk \ + && npm install -g node-gyp + +WORKDIR /app +# dep files +COPY packages packages +COPY package.json package-lock.json ./ +## Install deps +RUN bun install && bun --filter='./packages/genaiscript-rust-shim' run buildShim + + +FROM node:20-bookworm as app + +WORKDIR /app + +# Install playwright +ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/lib/chromium/chromium \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 +RUN npx -y playwright@1.49.1 install --with-deps chromium && npm install -g bun + + +COPY --from=node-builder /app/node_modules ./node_modules +COPY --from=node-builder /app/package.json . +COPY --from=agent-server-builder /build-context/target/aarch64-unknown-linux-musl/release/agent-server ./agent-server + +# Ensure the binary is executable +RUN chmod +x ./agent-core + +# copy agent source files +COPY packages/genaiscript-rust-shim/dist ./dist +COPY genaisrc ./genaisrc + +# Expose the required port +EXPOSE 3006 + +# Set the entrypoint to the Rust binary +ENTRYPOINT ["./agent-server"] diff --git a/Local.Dockerfile b/Local.Dockerfile new file mode 100644 index 0000000..ebd13e7 --- /dev/null +++ b/Local.Dockerfile @@ -0,0 +1,88 @@ +# Stage 1: Build the Rust agent-core binary +FROM rust:1-slim-bullseye as agent-server-builder + +WORKDIR /build-context + +# Install only the minimal required build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config \ + musl-tools \ + && rm -rf /var/lib/apt/lists/* + +# Build for musl to ensure static linking +RUN rustup target add aarch64-unknown-linux-musl + +# Copy only necessary files for building +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY assets ./assets + +# Build with musl target for static linking +RUN cargo build --release --target aarch64-unknown-linux-musl && \ + strip /build-context/target/aarch64-unknown-linux-musl/release/agent-server + +# Stage 2: Build Bun dependencies +FROM oven/bun:alpine as node-builder + +# Install system dependencies and node-gyp +RUN apk add --no-cache \ + ca-certificates \ + curl \ + unzip \ + git \ + python3 \ + py3-pip \ + build-base \ + pkgconf \ + cmake \ + nodejs \ + npm \ + bash \ + chromium \ + nss \ + freetype \ + freetype-dev \ + harfbuzz \ + ca-certificates \ + ttf-freefont \ + font-noto-emoji \ + nodejs \ + wqy-zenhei \ + && rm -rf /var/cache/* \ + && mkdir /var/cache/apk \ + && npm install -g node-gyp + +WORKDIR /app +# dep files +COPY packages packages +COPY package.json package-lock.json bun.lockb ./ +## Install deps +RUN bun install + + +FROM node:20-bookworm as app + +WORKDIR /app + +# Install playwright +ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/lib/chromium/chromium \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 +RUN npx -y playwright@1.49.1 install --with-deps chromium && npm install -g bun + + +COPY --from=node-builder /app/node_modules ./node_modules +COPY --from=node-builder /app/package.json . +COPY --from=node-builder /app/packages ./packages +COPY --from=agent-server-builder /build-context/target/aarch64-unknown-linux-musl/release/agent-server ./agent-server + +# Ensure the binary is executable +RUN chmod +x ./agent-server + +# copy agent source files +COPY packages/genaiscript-rust-shim/dist ./dist + +# Expose the required port +EXPOSE 3006 + +# Set the entrypoint to the Rust binary +ENTRYPOINT ["./agent-server"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8fae7a --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# web-agent-rs +Hacky genaiscript host for integration into conversational AI applications. + +### Disclaimer +This has not undergone a formal security assessment. You should do your own evaluation before using this. + +### How it works +1. A chat client specifies the URL to this host in their environment. +2. They send a request with their credentials to create a stream resource + + +## Docs +The [docs](./docs) folder is under construction \ No newline at end of file diff --git a/Remote.Dockerfile b/Remote.Dockerfile new file mode 100644 index 0000000..5a57a97 --- /dev/null +++ b/Remote.Dockerfile @@ -0,0 +1,88 @@ +# Stage 1: Build the Rust agent-server binary +FROM rust:1-slim-bullseye as agent-server-builder + +WORKDIR /build-context + +# Install only the minimal required build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config \ + musl-tools \ + && rm -rf /var/lib/apt/lists/* + +# Build for musl to ensure static linking +RUN rustup target add x86_64-unknown-linux-musl + +# Copy only necessary files for building +COPY Cargo.toml Cargo.lock ./ +COPY src ./src +COPY assets ./assets + +# Build with musl target for static linking +RUN cargo build --release --target x86_64-unknown-linux-musl && \ + strip /build-context/target/x86_64-unknown-linux-musl/release/agent-server + +# Stage 2: Build Bun dependencies +FROM oven/bun:alpine as node-builder + +# Install system dependencies and node-gyp +RUN apk add --no-cache \ + ca-certificates \ + curl \ + unzip \ + git \ + python3 \ + py3-pip \ + build-base \ + pkgconf \ + cmake \ + nodejs \ + npm \ + bash \ + chromium \ + nss \ + freetype \ + freetype-dev \ + harfbuzz \ + ca-certificates \ + ttf-freefont \ + font-noto-emoji \ + nodejs \ + wqy-zenhei \ + && rm -rf /var/cache/* \ + && mkdir /var/cache/apk \ + && npm install -g node-gyp + +WORKDIR /app +# dep files +COPY packages packages +COPY package.json package-lock.json bun.lockb ./ +## Install deps +RUN bun install + + +FROM node:20-bookworm as app + +WORKDIR /app + +# Install playwright +ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/lib/chromium/chromium \ + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 +RUN npx -y playwright@1.49.1 install --with-deps chromium && npm install -g bun + + +COPY --from=node-builder /app/node_modules ./node_modules +COPY --from=node-builder /app/package.json . +COPY --from=node-builder /app/packages ./packages +COPY --from=agent-server-builder /build-context/target/x86_64-unknown-linux-musl/release/agent-server ./agent-server + +# Ensure the binary is executable +RUN chmod +x ./agent-server + +# copy agent source files +COPY packages/genaiscript-rust-shim/dist ./dist + +# Expose the required port +EXPOSE 3006 + +# Set the entrypoint to the Rust binary +ENTRYPOINT ["./agent-server"] diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 0000000..1f04423 --- /dev/null +++ b/assets/index.html @@ -0,0 +1,62 @@ + + + + + Axum Server UI + + + + +

Axum Server UI

+ + +
+ + \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..515319e3dd376b1e0380775c575104343e80b139 GIT binary patch literal 562032 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 z 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%0A$Hz^w#b;Zg?@1A5}kE^gCE@Rlp33c=> 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 literal 0 HcmV?d00001 diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..ec80c5f --- /dev/null +++ b/compose.yml @@ -0,0 +1,32 @@ +services: + agent-server: + build: + context: . + dockerfile: Local.Dockerfile + ports: + - "3006:3006" + environment: + # Load environment variables from the .env file + # The .env file should contain OPENAI_API_KEY, BING_SEARCH_API_KEY, TAVILY_API_KEY, etc. + - OPENAI_API_BASE=${OPENAI_API_BASE} + - OPENAI_API_KEY=${OPENAI_API_KEY} + - GENAISCRIPT_MODEL_LARGE=${GENAISCRIPT_MODEL_LARGE} + - GENAISCRIPT_MODEL_SMALL=${GENAISCRIPT_MODEL_SMALL} + - BING_SEARCH_API_KEY=${BING_SEARCH_API_KEY} + - BING_SEARCH_API_KEY=${BING_SEARCH_API_KEY} + - TAVILY_API_KEY=${TAVILY_API_KEY} + - PERIGON_API_KEY=${PERIGON_API_KEY} + - SEARXNG_API_BASE_URL=${SEARXNG_API_BASE_URL} + - PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser + env_file: "./.env" + develop: + watch: + - action: sync + path: ./packages/genaiscript/genaisrc + target: /app/web/packages/genaiscript/genaisrc + ignore: + - node_modules/ + - action: rebuild + path: ./src + - action: rebuild + path: cargo.toml diff --git a/docs/tokens.md b/docs/tokens.md new file mode 100644 index 0000000..c2afdfa --- /dev/null +++ b/docs/tokens.md @@ -0,0 +1,77 @@ +# Authentication System Documentation + +## Overview + +This document outlines the token-based authentication system used in web-agent-rs. The system uses FIPS204 signatures to +generate secure session tokens containing user data. + +## Core Components + +TODO: In the meantime, here's some hamfisted knowledge. + + +```javascript +class FIPS204KeyPair { + constructor() { + this.publicKey = "FIPS204_PUBLIC_KEY"; // Placeholder + this.privateKey = "FIPS204_PRIVATE_KEY"; // Placeholder + } + + sign(data) { + // Placeholder for actual FIPS204 signing logic + return `FIPS204_${data}_SIGNED`; + } + + verify(data, signature) { + // Placeholder for actual FIPS204 verification + return true; + } +} + +/* NOTES: +- the public key needs to be retrievable, so it can be used to verify payload signature at the time of the request. +- the private key is disposed so it can't be used to create more signatures +- future tokens should use a completely new keypair + + +- The fips204 authentication scheme was selected for its performance, flexibility, and key-length. +- It would be wise to configure additional protections like ip whitelisting and rate limiting. +*/ + +// User object representing token payload data +const user = { + sub: "user123", + name: "John Doe", + email: "john@example.com", + roles: ["user"], + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour from now +}; + +const keyPair = new FIPS204KeyPair(); +const signature = keyPair.sign(JSON.stringify(user)); + +function createToken(payload, signature) { + const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64'); + const encodedSignature = Buffer.from(signature).toString('base64'); + return `${encodedPayload}.${encodedSignature}`; +} + +const token = createToken(user, signature); + + +async function createStreamRequest(eventHost = "https://agent.example.com") { + + const requestParams = { + // will automagically include the session token as a cookie, where it will be parsed by the agent server + credentials: "include" + } + + const response = await fetch(eventHost, requestParams); + + const {streamId} = await response.json(); + + // This stream id is then supplied as a path parameter to stream, the token is validated to ensure the stream belongs to the user, and the stream is returned. + return streamId; +} +``` \ No newline at end of file diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..b7fdefc --- /dev/null +++ b/fly.toml @@ -0,0 +1,54 @@ +app = "web-agent-rs" +primary_region = "iad" + +[deploy] + strategy = "rolling" + +[build] + dockerfile = "Remote.Dockerfile" + deploy-target = "app" + + + +[env] + OPENAI_API_KEY="" + OPENAI_API_BASE="" + GENAISCRIPT_MODEL_LARGE="" + GENAISCRIPT_MODEL_SMALL="" + GENAISCRIPT_MODEL_PROVIDER="" + + SEARXNG_API_BASE_URL="" + SEARXNG_PASSWORD="" + BING_SEARCH_API_KEY = "" + TAVILY_API_KEY = "" + PERIGON_API_KEY= "" + CEREBRAS_API_KEY = "" + CCC_API_KEY="" + HF_API_KEY="" + +[http_service] + internal_port = 3006 + force_https = true + auto_stop_machines = "suspend" + auto_start_machines = true + # automatic shutdown when not in use + min_machines_running = 0 + + [http_service.http_options] + idle_timeout = 180 + + + [[http_service.checks]] + interval = '30s' + timeout = '5s' + grace_period = '10s' + method = 'GET' + path = '/health' + +[[vm]] + size = "performance-1x" + + +[[mounts]] +source = "web_agent_app_mount" +destination = "/app/data" diff --git a/gitleaks-report.json b/gitleaks-report.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/gitleaks-report.json @@ -0,0 +1 @@ +[] diff --git a/package.json b/package.json new file mode 100644 index 0000000..8223549 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "web-agent-rs", + "type": "module", + "workspaces": ["packages/*"], + "private": true, + "scripts": { + "dev": "bunx kill-port 3006 && bun run build && cargo watch -x 'run src/main.rs'", + "ai:search": "genaiscript run packages/genaiscript/genaisrc/web-search.genai.mts --vars USER_INPUT='who won the 2024 election?'", + "shim:ai:search": "pnpm build && ./dist/shim.js --file=genaisrc/search.genai.mts USER_INPUT=\"Who won the 2024 presidential election?\"\n", + "ai:news": "genaiscript run packages/genaiscript/genaisrc/news-search.genai.mts --vars USER_INPUT='What are the latest updates and developments in the Ukraine war?'", + "ai:url:read": "genaiscript run packages/genaiscript/genaisrc/web-scrape.genai.mts --vars USER_INPUT='{\"url\":\"https://geoff.seemueller.io/about\",\"query\":\"Describe the details of the page.\", \"action\": \"read\"}'", + "ai:url:scrape": "npx genaiscript run packages/genaiscript/genaisrc/web-scrape.genai.mts --vars USER_INPUT='{\"url\":\"https://www.time4learning.com/homeschool-curriculum/high-school/eleventh-grade/math.html\",\"query\":\"What is on this page?\", \"action\": \"scrape\"}'", + "crypto:quote": "npx genaiscript run packages/genaiscript/genaisrc/finance-query.genai.mts --vars USER_INPUT='Get a quote for BTC'", + "crypto:news": "npx genaiscript run packages/genaiscript/genaisrc/finance-query.genai.mts --vars USER_INPUT='What is the news for Bitcoin?'", + "crypto:overview": "npx genaiscript run packages/genaiscript/genaisrc/finance-query.genai.mts --vars USER_INPUT='What are the trending symbols in the market?'", + "compose:up": "docker compose up --build", + "prod:logs": "fly logs", + "build": "(cd packages/genaiscript-rust-shim && bun run buildShim)" + } +} diff --git a/packages/core/index.ts b/packages/core/index.ts new file mode 100644 index 0000000..5eb5667 --- /dev/null +++ b/packages/core/index.ts @@ -0,0 +1,4 @@ +export * from "../genaiscript/genaisrc/_state"; +export * from "./news"; +export * from "./quotes"; +export * from "./types"; \ No newline at end of file diff --git a/packages/core/market/index.ts b/packages/core/market/index.ts new file mode 100644 index 0000000..18a5b81 --- /dev/null +++ b/packages/core/market/index.ts @@ -0,0 +1,14 @@ +import {ApiResponse} from "./types"; + +export async function collect_gainers_losers(x: { apiKey: string, limit: number }): Promise { + const { apiKey, limit } = x; + + // + const data: ApiResponse = await fetch(`https://pro-api.coinmarketcap.com/v1/cryptocurrency/trending/gainers-losers?limit=${limit}`, { + headers: { + "x-cmc_pro_api_key": apiKey + } + }).then((symbolDataRequest) => symbolDataRequest.json()); + + return data; +} \ No newline at end of file diff --git a/packages/core/market/types.ts b/packages/core/market/types.ts new file mode 100644 index 0000000..636c0c4 --- /dev/null +++ b/packages/core/market/types.ts @@ -0,0 +1,48 @@ +type Quote = { + price: number; + volume_24h: number; + percent_change_1h: number; + percent_change_24h: number; + percent_change_7d: number; + market_cap: number; + last_updated: string; +}; + +type Platform = null; + +type Tag = string; + +type Data = { + id: number; + name: string; + symbol: string; + slug: string; + cmc_rank?: number; + num_market_pairs: number; + circulating_supply: number; + total_supply: number; + max_supply: number; + last_updated: string; + date_added: string; + tags: Tag[]; + platform: Platform; + quote: { + USD: Quote; + BTC?: Quote; + ETH?: Quote; + }; +}; + +type Status = { + timestamp: string; + error_code: number; + error_message: string | null; + elapsed: number; + credit_count: number; +}; + +export type ApiResponse = { + data: Data[]; + status: Status; +}; + diff --git a/packages/core/news/index.ts b/packages/core/news/index.ts new file mode 100644 index 0000000..a383801 --- /dev/null +++ b/packages/core/news/index.ts @@ -0,0 +1,178 @@ +import {types, Instance} from 'mobx-state-tree'; +import {runInAction} from "mobx"; + + +const Article = types.model('Article', { + title: types.string, + content: types.string, + url: types.maybe(types.string), + source: types.maybe(types.string), + pubDate: types.maybe(types.string), + + summary: types.maybe(types.string), + description: types.maybe(types.string), + authorsByline: types.maybe(types.string), + shortSummary: types.maybe(types.string), + labels: types.maybe(types.frozen()), + imageUrl: types.maybe(types.string), + score: types.maybe(types.number), +}); + + +export const NewsStore = types + .model('NewsStore', { + symbolsNews: types.map(types.array(Article)), + isLoading: types.boolean, + error: types.maybe(types.string), + apiKey: types.string, + }) + .actions((self) => ({ + + addNews(symbol: string, articles: any[]) { + if (!self.symbolsNews.has(symbol)) { + self.symbolsNews.set(symbol, []); + } + + + const mappedArticles = articles.map((article) => Article.create({ + title: article.title || 'No Title', + content: article.content || 'No Content', + url: article.url, + source: article.domain, + pubDate: article.pubDate, + summary: article.summary, + description: article.description, + authorsByline: article.authorsByline, + shortSummary: article.shortSummary, + labels: article.labels, + imageUrl: article.imageUrl, + score: article.score, + + + })); + self.symbolsNews.get(symbol)!.push(...mappedArticles); + self.isLoading = false; + }, + + clearNews(symbol: string) { + if (self.symbolsNews.has(symbol)) { + self.symbolsNews.set(symbol, []); + } + }, + + setLoading(loading: boolean) { + self.isLoading = loading; + }, + + setError(message: string) { + self.error = message; + self.isLoading = false; + }, + + async fetchNewsForSymbol(symbol: string, limit: number, sort: "date" | "relevance") { + self.setLoading(true); + self.setError(undefined); + + try { + await runInAction(async () => { + const newsData = await collect_news({symbol, apiKey: self.apiKey, limit, sort}); + if (newsData && newsData.articles) { + self.addNews(symbol, newsData.articles); + } else { + self.setError("Failed to fetch news or invalid response format."); + } + }) + + + } catch (err: any) { + console.error('Error fetching news:', err); + self.setError(err.message || "Failed to fetch news."); + } + }, + })) + .views((self) => ({ + + getNewsForSymbol(symbol: string) { + + return self.symbolsNews.get(symbol) || []; + }, + + getAllSymbols() { + return Array.from(self.symbolsNews.keys()); + }, + + hasNewsForSymbol(symbol: string) { + return self.symbolsNews.has(symbol) && self.symbolsNews.get(symbol)!.length > 0; + }, + })); + +export type INewsStore = Instance; + + +export const createNewsStore = (apikey, perigon) => NewsStore.create({ + symbolsNews: {}, + isLoading: false, + error: undefined, + apiKey: apikey, +}); + +/* @collect_news return value structure +{ +news: { +status: 200, +numResults: 4080, +articles: [ + [Object], [Object], + [Object], [Object], + [Object], [Object], + [Object], [Object], + [Object], [Object] +] +} +} + */ +export async function collect_news(x: { symbol: string, apiKey: string, limit: number, sort: "date" | "relevance" }) { + + + const {symbol, apiKey, limit, sort} = x; + + const symbolNameMap = { + "BTC": "Bitcoin", + "ETH": "Ethereum", + "XRP": "Ripple", + "LTC": "Litecoin", + "ADA": "Cardano", + "DOGE": "Dogecoin", + "BNB": "Binance Coin", + "DOT": "Polkadot", + "SOL": "Solana", + "AVAX": "Avalanche" + }; + + + const cryptocurrencyName = symbolNameMap[symbol] ?? symbol; + + + const rawContentQuery = "scandal OR \"corporate misconduct*\" OR fraud OR \"financial irregularities*\" OR lawsuit OR \"legal action*\" OR bankruptcy OR \"financial distress*\" OR \"data breach\" OR \"security vulnerability*\" OR \"environmental impact\" OR \"ecological damage*\" OR \"labor dispute\" OR \"worker rights*\" OR \"product failure\" OR \"quality issue*\" OR \"ethical concern\" OR \"moral dilemma*\" OR \"health risk\" OR \"safety hazard*\" OR \"regulatory violation\" OR \"compliance issue*\" OR \"market manipulation\" OR \"trading irregularity*\" OR \"public relations crisis\" OR \"reputation damage*\" OR \"political controversy\" OR \"government intervention*\" OR \"consumer complaint\" OR \"customer dissatisfaction*\" OR \"supply chain disruption\" OR \"logistics problem*\" OR \"intellectual property dispute\" OR \"patent infringement*\""; + const contentQuery = encodeURIComponent(rawContentQuery); + + + const rawTitleQuery = `${cryptocurrencyName} OR ${symbol} OR "${cryptocurrencyName} price" OR "${cryptocurrencyName} market" OR "${cryptocurrencyName} news"`; + const titleQuery = encodeURIComponent(rawTitleQuery); + + try { + const result = await allNews({ + q: contentQuery, + title: titleQuery, + size: limit, + sortBy: sort, + apiKey: apiKey + }); + + + return result.data; + } catch (err) { + console.error('Error fetching news:', err); + throw err; + } +} diff --git a/packages/core/news/news.test.ts b/packages/core/news/news.test.ts new file mode 100644 index 0000000..3d4a3df --- /dev/null +++ b/packages/core/news/news.test.ts @@ -0,0 +1,75 @@ +import {describe, expect, it} from 'vitest'; +import {collect_news, createNewsStore, NewsStore} from './index'; + + +const testApiKey = ''; + +describe('NewsStore', () => { + it('should create a NewsStore instance', () => { + const store = createNewsStore(testApiKey); + expect(store).toBeDefined(); + expect(store.isLoading).toBe(false); + expect(store.error).toBeUndefined(); + expect(store.getAllSymbols()).toEqual([]); + }); + + it('should add news articles for a symbol', () => { + const store = createNewsStore(testApiKey); + const articles = [ + { title: 'Article 1', content: 'Content 1', url: 'http://example.com/1', source: 'Source 1', publishedAt: '2025-01-01' }, + { title: 'Article 2', content: 'Content 2', url: 'http://example.com/2', source: 'Source 2', publishedAt: '2025-01-02' } + ]; + + store.addNews('BTC', articles); + + expect(store.getNewsForSymbol('BTC')).toHaveLength(2); + expect(store.getNewsForSymbol('BTC')[0].title).toBe('Article 1'); + expect(store.getNewsForSymbol('BTC')[1].title).toBe('Article 2'); + expect(store.hasNewsForSymbol('BTC')).toBe(true); + }); + + it('should clear news articles for a symbol', () => { + const store = createNewsStore(testApiKey); + const articles = [ + { title: 'Article 1', content: 'Content 1', url: 'http://example.com/1', source: 'Source 1', publishedAt: '2025-01-01' } + ]; + + store.addNews('BTC', articles); + store.clearNews('BTC'); + + expect(store.getNewsForSymbol('BTC')).toHaveLength(0); + expect(store.hasNewsForSymbol('BTC')).toBe(false); + }); + + it('should handle fetchNewsForSymbol successfully', async () => { + const store = createNewsStore(testApiKey); + + await store.fetchNewsForSymbol('BTC', 10, 'date'); + + const storeNews = store.getNewsForSymbol('BTC'); + + console.log(storeNews); + + expect(storeNews).toHaveLength(10); + expect(store.getNewsForSymbol('BTC')[0].title).toBeTypeOf("string"); + expect(store.isLoading).toBe(false); + expect(store.error).toBeUndefined(); + }); + + + + + it('should throw an error for invalid symbol in collect_news', async () => { + await expect(collect_news({ symbol: 'INVALID', apiKey: testApiKey, limit: 10, sort: 'date' })) + .rejects.toThrow('Invalid symbol: INVALID. Must be one of BTC, ETH, XRP, LTC, ADA, DOGE, BNB, DOT, SOL, AVAX.'); + }); + + it('should fetch news using collect_news', async () => { + + const result = await collect_news({ symbol: 'BTC', apiKey: testApiKey, limit: 1, sort: 'date' }); + + expect(result).toBeDefined(); + expect(result.status).toBe(200); + expect(result.articles).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..88604b1 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,9 @@ +{ + "name": "@web-agent-rs/core", + "version": "1.0.0", + "type": "module", + "dependencies": { + "mobx-state-tree": "^7.0.2", + "@web-agent-rs/perigon": "workspace:*" + } +} \ No newline at end of file diff --git a/packages/core/portfolio/index.ts b/packages/core/portfolio/index.ts new file mode 100644 index 0000000..3609718 --- /dev/null +++ b/packages/core/portfolio/index.ts @@ -0,0 +1,137 @@ +import {getSnapshot, types} from "mobx-state-tree"; +import {NewsStore} from "../news"; + + +const PortfolioCashModel = types.model("PortfolioCash", { + amount: types.number, + currency: types.enumeration("Currency", ["USD", "BTC"]), +}); + + +const PortfolioActionModel = types.model("PortfolioAction", { + action: types.enumeration("Action", ["buy", "sell", "hold"]), + symbol: types.string, + quantity: types.number, + timestamp: types.Date, +}); + + +export const PortfolioNewsportfolioNewsModel = types.model("PortfolioNews", { + symbol: types.string, + date_created: types.string, + news: types.array(types.model("NewsItem", { + symbol: types.maybe(types.string), + date_created: types.maybe(types.string), + news: types.string, + timestamp: types.maybe(types.string), + })), + timestamp: types.maybe(types.string), +}); + +export const portfolioQuoteModel = types.model("PortfolioQuote", { + symbol: types.string, + quote: types.string, + date_created: types.string, +}); + + +const PortfolioAssetContextModel = types.model("PortfolioAssetContext", { + timestamp: types.Date, + portfolio_snapshot: PortfolioCashModel, +}); + + +const PortfolioAssetModel = types.model("PortfolioAsset", { + symbol: types.string, + quantity: types.number, + recommended_action: types.maybe( + types.enumeration("RecommendedAction", ["buy", "sell", "hold"]) + ), + last_taken_action: types.optional(types.enumeration("LastAction", ["buy", "sell", "hold", "none", "never"]), "never"), + context: PortfolioAssetContextModel, +}); + + +const PortfolioModel = types + .model("Portfolio", { + supportedSymbols: types.array(types.string), + liquidity: PortfolioCashModel, + actions: types.optional(types.array(PortfolioActionModel), []), + assets: types.array(PortfolioAssetModel), + news: types.optional(types.array(NewsStore), []), + quotes: types.optional(types.array(portfolioQuoteModel), []), + }) + .actions((self) => ({ + + addAction(actionData: { + action: "buy" | "sell" | "hold"; + symbol: string; + quantity: number; + }) { + self.actions.push({ + ...actionData, + timestamp: new Date(), + }); + }, + addNews(newsData: any) { + self.news.push({ + ...newsData, + timestamp: new Date(), + }); + }, + addQuote(quoteData: any) { + self.quotes + self.quotes.push({ + ...quoteData, + timestamp: new Date(), + }); + }, + + updateLiquidity(amount: number) { + self.liquidity.amount = amount; + }, + })); + +const tokenList = [ + "AAVE", "AVAX", "BAT", "BCH", "BTC", + "CRV", "DOGE", "DOT", "ETH", "GRT", + "LINK", "LTC", "MKR", "SHIB", "SUSHI", + "UNI", "USDC", "USDT", "XTZ", "YFI", +]; + + +const portfolioCash = PortfolioCashModel.create({ + amount: 10000, + currency: "USD", +}); + + +const portfolioAssets = tokenList.map((token) => + PortfolioAssetModel.create({ + symbol: token, + quantity: 0, + recommended_action: "hold", + last_taken_action: undefined, + context: { + timestamp: new Date(), + portfolio_snapshot: getSnapshot(portfolioCash), + }, + }) +); + + +const portfolioActions = []; +const portfolioNews = []; +const portfolioQuotes = []; + + +const portfolioInstance = PortfolioModel.create({ + liquidity: portfolioCash, + actions: portfolioActions, + assets: portfolioAssets, + supportedSymbols: tokenList, + news: portfolioNews, + quotes: portfolioQuotes +}); + +export default portfolioInstance; diff --git a/packages/core/quotes/index.ts b/packages/core/quotes/index.ts new file mode 100644 index 0000000..c911100 --- /dev/null +++ b/packages/core/quotes/index.ts @@ -0,0 +1,13 @@ +import {ApiResponse} from "./types"; + +export async function collect_quote(x: { symbol: string, apiKey: string }) { + const {symbol, apiKey} = x; + + const data: ApiResponse = await fetch(`https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest?symbol=${symbol}`, { + headers: { + "x-cmc_pro_api_key": apiKey + } + }).then((symbolDataRequest) => symbolDataRequest.json()); + + return data; +} \ No newline at end of file diff --git a/packages/core/quotes/models.ts b/packages/core/quotes/models.ts new file mode 100644 index 0000000..a38c9d5 --- /dev/null +++ b/packages/core/quotes/models.ts @@ -0,0 +1,77 @@ +import {types, flow, Instance} from "mobx-state-tree"; +import {collect_quote} from './index'; + + +const QuoteData = types.optional(types.frozen(), {}) + +export const QuoteStore = types + .model("QuoteStore", { + apiKey: types.string, + quotes: types.map(QuoteData), + }) + .views(self => ({ + getQuote(symbol) { + return self.quotes.get(symbol); + }, + + hasQuote(symbol) { + return self.quotes.has(symbol); + } + })) + .actions(self => { + + const extractUsefulData = (data, symbol) => { + return data.data[symbol].map(qd => ({ + symbol: qd.symbol, + slug: qd.slug, + tags: qd.tags, + id: qd.id, + ...qd.quote.USD + })).at(0); + }; + + const fetchQuote = flow(function* (symbol) { + try { + const data = yield collect_quote({symbol, apiKey: self.apiKey}); + const usefulData = extractUsefulData(data, symbol); + + + self.quotes.set(symbol, usefulData); + + return usefulData; + } catch (error) { + console.error(`An error occurred fetching the quote for symbol: ${symbol}`, error); + throw error; + } + }); + + const fetchQuotes = flow(function* (symbols) { + const results = {}; + + for (const symbol of symbols) { + + if (self.quotes.has(symbol)) { + results[symbol] = self.quotes.get(symbol); + } else { + + const data = yield fetchQuote(symbol); + results[symbol] = extractUsefulData(data, symbol); + } + } + + return results; + }); + + const clearCache = () => { + self.quotes.clear(); + }; + + return { + fetchQuote, + fetchQuotes, + clearCache + }; + }); + + +export type QuoteManagerType = Instance; \ No newline at end of file diff --git a/packages/core/quotes/quote.test.ts b/packages/core/quotes/quote.test.ts new file mode 100644 index 0000000..6e79ac3 --- /dev/null +++ b/packages/core/quotes/quote.test.ts @@ -0,0 +1,17 @@ +import {describe, it} from 'vitest'; +import {QuoteStore} from "./models"; + +describe('QuoteStore', () => { + it('should get data for symbols using the quoteManager', async () => { + const testApiKey = ''; + const quoteManager = QuoteStore.create({ + apiKey: testApiKey, + }); + + const symbol = 'BTC'; + + const data = await quoteManager.fetchQuote(symbol); + + console.log(JSON.stringify(data)); + }); +}); \ No newline at end of file diff --git a/packages/core/quotes/types.ts b/packages/core/quotes/types.ts new file mode 100644 index 0000000..06228fd --- /dev/null +++ b/packages/core/quotes/types.ts @@ -0,0 +1,71 @@ +type Status = { + timestamp: string; + error_code: number; + error_message: string | null; + elapsed: number; + credit_count: number; + notice: string | null; +}; + +type Tag = { + slug: string; + name: string; + category: string; +}; + +type Quote = { + USD: { + price: number | null; + volume_24h: number; + volume_change_24h: number; + percent_change_1h: number; + percent_change_24h: number; + percent_change_7d: number; + percent_change_30d: number; + percent_change_60d: number; + percent_change_90d: number; + market_cap: number | null; + market_cap_dominance: number | null; + fully_diluted_market_cap: number | null; + tvl: number | null; + last_updated: string; + }; +}; + +type Platform = { + id: number; + name: string; + symbol: string; + slug: string; + token_address: string; +}; + +type Cryptocurrency = { + id: number; + name: string; + symbol: string; + slug: string; + num_market_pairs: number; + date_added: string; + tags: Tag[]; + max_supply: number | null; + circulating_supply: number | null; + total_supply: number; + platform: Platform | null; + is_active: number; + infinite_supply: boolean; + cmc_rank: number | null; + is_fiat: number; + self_reported_circulating_supply: number | null; + self_reported_market_cap: number | null; + tvl_ratio: number | null; + last_updated: string; + quote: Quote; +}; + +export type ApiResponse = { + status: Status; + data: { + [SYMBOL: string]: Cryptocurrency[]; + }; +}; \ No newline at end of file diff --git a/packages/core/types/index.ts b/packages/core/types/index.ts new file mode 100644 index 0000000..9654a7c --- /dev/null +++ b/packages/core/types/index.ts @@ -0,0 +1,69 @@ +export type CryptoDataResponse = { + status: { + timestamp: string; + error_code: number; + error_message: string | null; + elapsed: number; + credit_count: number; + notice: string | null; + }; + data: { + [key: string]: CryptoAsset[]; + }; +}; + +export type CryptoAsset = { + id: number; + name: string; + symbol: string; + slug: string; + num_market_pairs: number; + date_added: string; + tags: CryptoTag[]; + max_supply: number | null; + circulating_supply: number; + total_supply: number; + platform: CryptoPlatform | null; + is_active: number; + infinite_supply: boolean; + cmc_rank: number; + is_fiat: number; + self_reported_circulating_supply: number | null; + self_reported_market_cap: number | null; + tvl_ratio: number; + last_updated: string; + quote: { + [currency: string]: CryptoQuote; + }; +}; + +export type CryptoTag = { + slug: string; + name: string; + category: string; +}; + +export type CryptoPlatform = { + id: number; + name: string; + symbol: string; + slug: string; + token_address: string; +}; + +export type CryptoQuote = { + price: number; + volume_24h: number; + volume_change_24h: number; + percent_change_1h: number; + percent_change_24h: number; + percent_change_7d: number; + percent_change_30d: number; + percent_change_60d: number; + percent_change_90d: number; + market_cap: number; + market_cap_dominance: number; + fully_diluted_market_cap: number; + tvl: number; + last_updated: string; +}; \ No newline at end of file diff --git a/packages/genaiscript-rust-shim/.gitignore b/packages/genaiscript-rust-shim/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/packages/genaiscript-rust-shim/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/genaiscript-rust-shim/README.md b/packages/genaiscript-rust-shim/README.md new file mode 100644 index 0000000..7a20ecc --- /dev/null +++ b/packages/genaiscript-rust-shim/README.md @@ -0,0 +1,15 @@ +# genaiscript-rust-shim + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.1.36. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/packages/genaiscript-rust-shim/bun.lockb b/packages/genaiscript-rust-shim/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..9feb5b8e53081caf595802ded237f0477e8b3080 GIT binary patch literal 3144 zcmd5;eN0+nF(t!7N_p)b^aI~}<-=gAPl!q7w}Wg#d#7TAH%RvfnEo6J+}{RopvD!9pPm~(l2_}gmvtal>SjXuJ5mW)V{fj*mpRz z_1T!UW@Xo%@Dq#vbC}9P+?5JP#D8;o7s%mWOlrI3Uq~DS(fMxp+OP1!Ff8KOd|w zv5|a-q_>IiJrp#1=<<*@$Iw~0<#t8)r7^!#XAr^4Qc`-==~goALedj^s-}9OoUi*HjM{ zZ&=qCrF%X9>?bu@o!^kJUJVE^=pEIXurv=|&pZh-j@)x-`>zC!V!D{!T|TXE&4hME z*@S27y8CJyeZ3#=WZs=R6>``+QnBOodp}Qq8h9`xc+AlvEXzE3?0!qpc@JIzau#So z6mHXNwe3dYd`eZ&-JAF2raSzSk-H8_Pj>scY`?03+c|zouLVz!*lNE@Q4^v48LJXY z?Fk1y{BpKoZ|^$_4_;UoNCd{=A^+Czk^@wI7bZ>z|CGAvNc%XmXZVDfP5aoMuC@-R z*>k39_Wd=`qR?KHsY+)*W5Szprsqay<vu&w!rRq4Td`LtO{&sA%a(j_ob^!3M zl%TjHy&OMQ(Ea7OyHc1l++Fk|r-e#}*5Z_jVKoK=j$T|mg(k^(kyIjLs3Ov=+a{8U zGzLmaP*-vs@!%ncB2=bH6>3W~IF(}Fr-%nnh2C}aPH*6)%W(-V!7$VrpqJi@Z_uzL z!(tff70}zy=SARB2*K*XfL#Q7{rS9-Xk3m*NHEk_pgw`m3x_TOL7fKb8~{dvOQ8!3 zV=B25UYtLvXG$5;m`Bq1^93wPn>2Sh* z(xjmnEluUJV%oy6VrD;OWD9di*It5iN`O{yKg;Gau>?VrdWvD`Lfn*V%w}*ittZ?@ zgczENo#Dq>eGyzZ85Tj$+i+l~y_^Dg_CLf50J&DT~rl zOZwz#253B&VplOhvZdX~X8<~%5j%@c=8T9lpROD(f_G;C5V#j&IUa>aLLO(4{15;A E4G|wzqyPW_ literal 0 HcmV?d00001 diff --git a/packages/genaiscript-rust-shim/genaiscript-rust-shim.ts b/packages/genaiscript-rust-shim/genaiscript-rust-shim.ts new file mode 100644 index 0000000..4197a75 --- /dev/null +++ b/packages/genaiscript-rust-shim/genaiscript-rust-shim.ts @@ -0,0 +1,61 @@ +#!/usr/bin/env node +import minimist from "minimist"; +import { run } from "genaiscript/api"; +import { RunScriptOptions } from "./shim-types"; + +type Args = { + file: string; + vars: Record; + options: Partial & { + envVars?: Record; + signal?: AbortSignal; + }; +}; + +async function wrapper(args: Args) { + try { + await run(args.file, [], { vars: args.vars }, args.options); + } catch (error) { + console.error("Error executing script:", error); + process.exit(1); + } +} + +function parseCliArgs(): Args { + const argv = minimist(process.argv.slice(2), { + string: ["file"], + alias: { f: "file" }, + }); + + if (!argv.file) { + console.error("Error: Missing required argument --file"); + process.exit(1); + } + + const keyValuePairs = argv._; + + const vars: Record = keyValuePairs.reduce((acc, pair) => { + const [key, value] = pair.split("="); + if (key && value !== undefined) { + acc[key] = value; // Retain the `unknown` type for later flexibility + } else { + console.error(`Error: Invalid key=value pair "${pair}"`); + process.exit(1); + } + return acc; + }, {} as Record); + + return { + file: argv.file, + vars, + options: {}, + }; +} + +async function main() { + + const args = parseCliArgs(); + await wrapper(args); +} + +main(); diff --git a/packages/genaiscript-rust-shim/index.ts b/packages/genaiscript-rust-shim/index.ts new file mode 100644 index 0000000..598f124 --- /dev/null +++ b/packages/genaiscript-rust-shim/index.ts @@ -0,0 +1,2 @@ +import './genaiscript-rust-shim'; +export * from './genaiscript-rust-shim'; diff --git a/packages/genaiscript-rust-shim/package.json b/packages/genaiscript-rust-shim/package.json new file mode 100644 index 0000000..959e8b0 --- /dev/null +++ b/packages/genaiscript-rust-shim/package.json @@ -0,0 +1,19 @@ +{ + "name": "@web-agent-rs/genaiscript-rust-shim", + "module": "index.ts", + "private": true, + "type": "module", + "scripts": { + "buildShim": "esbuild genaiscript-rust-shim.ts --bundle --format=esm --packages=external --outdir=dist --platform=node && chmod +x dist/genaiscript-rust-shim.js", + "setupDev": "cp dist/genaiscript-rust-shim.js ../../dist/genaiscript-rust-shim.js" + }, + "devDependencies": { + "@types/bun": "latest", + "minimist": "^1.2.8", + "genaiscript": "^1.95.1", + "esbuild": "^0.24.2" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/packages/genaiscript-rust-shim/shim-types.ts b/packages/genaiscript-rust-shim/shim-types.ts new file mode 100644 index 0000000..925bf94 --- /dev/null +++ b/packages/genaiscript-rust-shim/shim-types.ts @@ -0,0 +1,70 @@ +type FenceFormat = "markdown" | "xml" | "none" + +export interface WorkspaceFile { + /** + * Name of the file, relative to project root. + */ + filename: string + + /** + * Content mime-type if known + */ + type?: string + + /** + * Encoding of the content + */ + encoding?: "base64" + + /** + * Content of the file. + */ + content?: string +} + +export interface RunScriptOptions { + excludedFiles: string[] + excludeGitIgnore: boolean + runRetry: string + out: string + retry: string + retryDelay: string + maxDelay: string + json: boolean + yaml: boolean + outTrace: string + outOutput: string + outAnnotations: string + outChangelogs: string + pullRequest: string + pullRequestComment: string | boolean + pullRequestDescription: string | boolean + pullRequestReviews: boolean + outData: string + label: string + temperature: string | number + topP: string | number + seed: string | number + maxTokens: string | number + maxToolCalls: string | number + maxDataRepairs: string | number + model: string + smallModel: string + visionModel: string + embeddingsModel: string + modelAlias: string[] + provider: string + csvSeparator: string + cache: boolean | string + cacheName: string + applyEdits: boolean + failOnErrors: boolean + removeOut: boolean + vars: string[] | Record + fallbackTools: boolean + jsSource: string + logprobs: boolean + topLogprobs: number + fenceFormat: FenceFormat + workspaceFiles?: WorkspaceFile[] +} diff --git a/packages/genaiscript-rust-shim/tsconfig.json b/packages/genaiscript-rust-shim/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/packages/genaiscript-rust-shim/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/packages/genaiscript/genaisrc/.gitignore b/packages/genaiscript/genaisrc/.gitignore new file mode 100644 index 0000000..6641d96 --- /dev/null +++ b/packages/genaiscript/genaisrc/.gitignore @@ -0,0 +1,4 @@ +# auto-generated +genaiscript.d.ts +tsconfig.json +jsconfig.json \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/_state/index.ts b/packages/genaiscript/genaisrc/_state/index.ts new file mode 100644 index 0000000..ecee7e2 --- /dev/null +++ b/packages/genaiscript/genaisrc/_state/index.ts @@ -0,0 +1,21 @@ +import {types} from "mobx-state-tree"; +import {QuoteStore} from "@web-agent-rs/core/quotes/models"; +import {NewsStore} from "@web-agent-rs/core/news"; + +import newsStore from "./news"; +import quoteStore from "./quotes"; + + +const StateModel = types.model("State", { + symbols: types.array(types.string), + quotes: QuoteStore, + news: NewsStore, +}); + + +const state = StateModel.create({ + quotes: quoteStore, + news: newsStore, +}); + +export default state; \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/_state/news.ts b/packages/genaiscript/genaisrc/_state/news.ts new file mode 100644 index 0000000..2cdcb2a --- /dev/null +++ b/packages/genaiscript/genaisrc/_state/news.ts @@ -0,0 +1,11 @@ +import {NewsStore} from "@web-agent-rs/core/news"; +import {Instance} from "mobx-state-tree"; + +const newsStore = NewsStore.create({ + isLoading: false, + apiKey: process.env.PERIGON_API_KEY +}); + +export type NewsStore = Instance; + +export default newsStore; \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/_state/quotes.ts b/packages/genaiscript/genaisrc/_state/quotes.ts new file mode 100644 index 0000000..1d7419f --- /dev/null +++ b/packages/genaiscript/genaisrc/_state/quotes.ts @@ -0,0 +1,7 @@ +import {QuoteStore} from "@web-agent-rs/core/quotes/models"; + +const quoteStore = QuoteStore.create({ + apiKey: process.env.CCC_API_KEY +}); + +export default quoteStore; \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/agent.genai.mts b/packages/genaiscript/genaisrc/agent.genai.mts new file mode 100644 index 0000000..d630930 --- /dev/null +++ b/packages/genaiscript/genaisrc/agent.genai.mts @@ -0,0 +1,24 @@ +import {PerigonClient as NewsSearchTool} from "@agentic/perigon"; + +script({ + system: ["system.tools"], + tools: "agent", + maxTokens: 8192 +}) + +const newSearchTool = new NewsSearchTool(); + +defTool(newSearchTool) + +$`You are a chat assistant that uses agent tools to solve problems. + +while true: + - ask the user for a question using the agent_user_input + - make a plan to answer the question step by step + - answer the question +end while + +## guidance: + - use the agent tools to help you + - do NOT try to ask the user questions directly, use the agent_user_input tool instead. +` \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/deep-research.genai.mts b/packages/genaiscript/genaisrc/deep-research.genai.mts new file mode 100644 index 0000000..1a15d84 --- /dev/null +++ b/packages/genaiscript/genaisrc/deep-research.genai.mts @@ -0,0 +1,281 @@ +import {task, entrypoint, interrupt, MemorySaver} from "@langchain/langgraph" +import "./tools/searxng.genai.mjs" +import {SearxngClient} from "@agentic/searxng"; + + +script({ + title: "Deep Research Program", + description: "Researchers can use this program to conduct deep research on a topic", + model: "large", + cache: "ephemeral", +}) +const {output, vars} = env + + +const breakdownResearch = task( + "breakdown_research", + async (question: string) => { + const result = await runPrompt( + async (ctx) => { + ctx.$`You are an expert research strategist. + +Task: Break down the following research question into 3-5 focused sub-questions that would help comprehensively answer the main question. + +Research question: ${question} + +For each sub-question: +1. Assign a unique ID (e.g., SQ1, SQ2) +2. Explain the rationale for why this sub-question is important +3. Ensure the sub-questions collectively cover the main research question + +Output the breakdown as a JSON object.` + }, + { + label: "breakdown research", + responseSchema: { + type: "object", + properties: { + mainQuestion: {type: "string"}, + subQuestions: { + type: "array", + items: { + type: "object", + properties: { + id: {type: "string"}, + question: {type: "string"}, + rationale: {type: "string"}, + }, + }, + }, + }, + }, + } + ) + + return result.json + } +) + +const globalCtx = this; + + +const researchSubQuestion = task( + "research_subquestion", + async (subQuestion: { id: string; question: string }) => { + + const searxng = new SearxngClient({apiBaseUrl: "https://search-engine-gsio.fly.dev"}); + + const {text} = await runPrompt( + (_) => { + _.defTool(searxng) + _.$`You are an expert researcher with access to comprehensive information. + +Task: Thoroughly research the following question and provide a detailed answer. + +Question ID: ${subQuestion.id} +Question: ${subQuestion.question} + +Provide your findings in a structured format that includes: +- Your answer to the sub-question +- Relevant sources that support your answer +- Your confidence level in the answer (0-1)` + }, + { + model: "small", + label: `research subquestion ${subQuestion.id}`, + maxDataRepairs: 2, + responseSchema: { + type: "object", + properties: { + subQuestionId: {type: "string"}, + answer: {type: "string"}, + sources: { + type: "array", + items: { + type: "object", + properties: { + title: {type: "string"}, + url: {type: "string"}, + relevance: {type: "string"}, + }, + }, + }, + confidence: {type: "number"}, + }, + }, + } + ) + return text + } +) + + +const synthesizeFindings = task( + "synthesize_findings", + async (mainQuestion: string, findings: any[]) => { + const result = await runPrompt( + async (ctx) => { + ctx.$`You are an expert research synthesizer. + +Task: Synthesize the following research findings into a coherent response to the main research question. + +Main Research Question: ${mainQuestion} + +Findings: +${JSON.stringify(findings, null, 2)} + +Provide a synthesis that: +1. Directly answers the main research question +2. Integrates the findings from all sub-questions +3. Identifies limitations in the current research +4. Suggests next steps for further investigation` + }, + { + label: "synthesize findings", + responseType: "markdown", + responseSchema: { + type: "object", + properties: { + summary: {type: "string"}, + findings: {type: "array", items: {type: "string"}}, + limitations: { + type: "array", + items: {type: "string"}, + }, + nextSteps: {type: "array", items: {type: "string"}}, + }, + }, + } + ) + + return result.json + } +) + + +const summarizeAndIdentifyGaps = task( + "summarize_and_identify_gaps", + async (synthesis: any, findings: any[]) => { + const result = await runPrompt( + async (ctx) => { + ctx.$`You are an expert research evaluator. + +Task: Review the research synthesis and identify any gaps or areas that need deeper investigation. + +Current synthesis: +${JSON.stringify(synthesis, null, 2)} + +Research findings: +${JSON.stringify(findings, null, 2)} + +Please provide: +1. A concise summary of current findings +2. Identify 2-3 specific knowledge gaps +3. Formulate follow-up questions to address these gaps` + }, + { + label: "identify research gaps", + responseSchema: { + type: "object", + properties: { + summary: {type: "string"}, + gaps: { + type: "array", + items: {type: "string"}, + }, + followUpQuestions: { + type: "array", + items: { + type: "object", + properties: { + id: {type: "string"}, + question: {type: "string"}, + }, + }, + }, + }, + }, + } + ) + return result.json + } +) + + +const researchWorkflow = entrypoint( + {checkpointer: new MemorySaver(), name: "research_workflow"}, + async (input: { question: string; context?: string }) => { + + const breakdown = await breakdownResearch(input.question) + + + const subQuestionFindings = [] + + for (const sq of breakdown.subQuestions) { + const analysis = await researchSubQuestion(sq); + console.log(analysis); + subQuestionFindings.push(analysis); + } + + + let synthesis = await synthesizeFindings( + input.question, + subQuestionFindings + ) + + const gapAnalysis = await summarizeAndIdentifyGaps( + synthesis, + subQuestionFindings + ) + + + const followUpFindings = []; + for (const fq of gapAnalysis.followUpQuestions) { + const anwser = await researchSubQuestion(fq); + console.log(anwser); + followUpFindings.push(anwser); + } + + + const allFindings = [...subQuestionFindings, ...followUpFindings] + const finalSynthesis = await synthesizeFindings( + input.question, + allFindings + ) + + + return { + question: input.question, + breakdown: breakdown, + initialFindings: subQuestionFindings, + gapAnalysis: gapAnalysis, + followUpFindings: followUpFindings, + synthesis: finalSynthesis, + } + } +) + + +const researchQuestion = + env.vars.question || + "What are the most promising approaches to climate change mitigation?" + + +const threadId = `research-${Date.now()}` + + +const config = { + configurable: { + thread_id: threadId, + }, +} + + +const results = await researchWorkflow.invoke( + { + question: researchQuestion, + context: vars.context || "", + }, + config +) +output.fence(results, "json") \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/finance-query.genai.mts b/packages/genaiscript/genaisrc/finance-query.genai.mts new file mode 100644 index 0000000..40a0583 --- /dev/null +++ b/packages/genaiscript/genaisrc/finance-query.genai.mts @@ -0,0 +1,80 @@ +import state from "./_state/index.js"; +import {getSnapshot} from "mobx-state-tree"; +import {collect_gainers_losers} from "@web-agent-rs/core/market"; + +def("QUERY", env.vars.user_input); + + +defTool( + "get_quote", + "Fetch quote for symbol", + { + "symbol": { + type: "string", + default: "BTC" + } + }, + async (args) => { + const { symbol } = args; + await state.quotes.fetchQuote(symbol); + + + const quote = await state.quotes.getQuote(symbol); + + return JSON.stringify(quote) + } +); + +defTool( + "get_news", + "Fetches news for symbol", + { + "symbol": { + type: "string", + default: "BTC" + } + }, + async (args) => { + const { symbol } = args; + await state.news.fetchNewsForSymbol(symbol, 5, "date"); + + const news = await state.news.getNewsForSymbol(symbol).map(i => getSnapshot(i)); + + return news + } +); + + +defTool( + "get_market", + "Fetches trending symbols of market", + { + "limit": { + type: "number", + default: "25" + } + }, + async (args) => { + + const { limit } = args; + + + const marketOverviewRequest = await collect_gainers_losers({apiKey: process.env.CCC_API_KEY, limit: parseInt(limit) }) + + return marketOverviewRequest.data.map(item => ({ + symbol: item.symbol, + name: item.name, + + + change_1h: item.quote.USD.percent_change_1h, + price: item.quote.USD.price, + volume_24h: item.quote.USD.volume_24h + })) + } +); + + + +$`You are a market data assistant specializing in financial analysis. Respond to QUERIES with accurate, clear, and concise information relevant to professionals in the finance sector. Use available tools efficiently to gather and present quantitative data.`; + + diff --git a/packages/genaiscript/genaisrc/image-generator.genai.mts b/packages/genaiscript/genaisrc/image-generator.genai.mts new file mode 100644 index 0000000..a9e1724 --- /dev/null +++ b/packages/genaiscript/genaisrc/image-generator.genai.mts @@ -0,0 +1,11 @@ +console.log("Generating image") + +def("USER_INPUT", env.vars.user_input); + + +const inputs = { + host: JSON.parse(env.vars.user_input).host, + imageId: JSON.parse(env.vars.user_input).imageId +} + +console.log(`![Generated Image](/generated/image/${inputs.imageId})`); diff --git a/packages/genaiscript/genaisrc/incubator/firecrawl.genai.mts b/packages/genaiscript/genaisrc/incubator/firecrawl.genai.mts new file mode 100644 index 0000000..6bb9ea5 --- /dev/null +++ b/packages/genaiscript/genaisrc/incubator/firecrawl.genai.mts @@ -0,0 +1,26 @@ +script({ + title: "Stock Market News Scraper", + tools: ["searxng"], +}) + +defTool({ + "mcp-server-firecrawl": { + command: "npx", + args: ["-y", "firecrawl-mcp"], + }, +}) + +def("QUERY_NEWS", "Latest news on AAPL") +def("QUERY_SENTIMENT", "Market sentiment for technology sector") + + +$`Search the query with searxng: QUERY_NEWS` + + +$`Scrape the top search result with firecrawl` + + +$`Search the query with searxng: QUERY_SENTIMENT` + + +$`Scrape the top search result with firecrawl` \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/news-search.genai.mts b/packages/genaiscript/genaisrc/news-search.genai.mts new file mode 100644 index 0000000..4ab41be --- /dev/null +++ b/packages/genaiscript/genaisrc/news-search.genai.mts @@ -0,0 +1,24 @@ +import {SearxngClient} from "@agentic/searxng"; +import "./tools/searxng.genai.mjs" + +script({ + title: "news_search_agent", + tools: ["searxng"], + maxToolCalls: 2, + cache: false, +}); + +def("USER_INPUT", env.vars.user_input); +def("TODAY", new Date().toISOString().split("T")[0]); +def("LINK_FORMAT", "[Link](url)"); + + +$`You are an assistant searching for news using complex queries to pinpoint results. + + +- tailor search to answer the question in USER_INPUT +- perform 2 searches in parallel sorted by relevance and date respectively +- create a markdown table of <=5 results of both searches +- header row: Date, Title, Summary, and Link + +Respond with a single table, no extra text.` diff --git a/packages/genaiscript/genaisrc/tools/searxng.genai.mts b/packages/genaiscript/genaisrc/tools/searxng.genai.mts new file mode 100644 index 0000000..5d400ed --- /dev/null +++ b/packages/genaiscript/genaisrc/tools/searxng.genai.mts @@ -0,0 +1,18 @@ +script({ + isSystem: true +}) + +import {SearxngClient} from "@agentic/searxng"; +import ky from 'ky'; + +const kyWithHeaders = ky.create({ + referrerPolicy: "unsafe-url", + + headers: { + 'Authorization': 'Basic ' + btoa(`admin:${process.env.SEARXNG_PASSWORD}`), + } +}); + +const searxng = new SearxngClient({ky: kyWithHeaders}); + +defTool(searxng) \ No newline at end of file diff --git a/packages/genaiscript/genaisrc/web-scrape.genai.mts b/packages/genaiscript/genaisrc/web-scrape.genai.mts new file mode 100644 index 0000000..726c801 --- /dev/null +++ b/packages/genaiscript/genaisrc/web-scrape.genai.mts @@ -0,0 +1,88 @@ +import {Window} from 'happy-dom'; +import {platform} from 'os'; + +script({ + title: "scrape", + cache: false, +}); + +/* + "url": "Full URL in the conversation that references the URL being interacted with. No trailing slash!", + "query": "Implied question about the resources at the URL.", + "action": "read | scrape | crawl" +*/ + +try { + const {url, query, action} = JSON.parse(env.vars.user_input); +} catch (e) { + throw "Sorry! Something went wrong."; +} + +const {url, query, action} = JSON.parse(env.vars.user_input); + +def("URL", url); + +def("QUERY", query); + +def("ACTION", action); + +// console.log({url, query, action}); + +if(!(new URL(url) ?? undefined)) { + throw "Bad URL. Maybe try again?" +} + +function getBrowser(): "webkit" | "chromium" | "firefox" { + if (platform() === 'darwin') { + return "webkit"; // macOS is identified by 'darwin' + } + return "chromium"; // default to chromium for other platforms +} + +const {text} = await host.fetchText(new URL(url).toString()); + +// const browser = getBrowser(); + +// const page = await host.browse(new URL(url).toString(), { +// browser: getBrowser(), +// headless: true, +// javaScriptEnabled: browser !== "chromium", +// // timeout: 3000, +// // bypassCSP: true, +// // baseUrl: new URL(url).origin, +// }); +// +// const html = (await page.content()); +// const title = (await page.title()); + +// console.log({html}); + +const window = new Window({ + // url: "http://localhost:8080", + height: 1920, + width: 1080, + settings: { + navigator: { + userAgent: 'Mozilla/5.0 (compatible; GeoffsAI/1.0; +https://geoff.seemueller.io)', + }, + } +}); + +window.document.body.innerHTML = text; + +const textContent = window.document.body.textContent; + +def("PAGE_TEXT", textContent); + +$`You a helpful assistant interacting with resources found at the URL. + +- markdown table is concise representation of PAGE_TEXT relevant to the QUERY + +### Respond Example: +### Data from ${url}: +| Header 1 | Header 2 | Header 3 | +|----------|----------|----------| +| Data 1 | Data 2 | Data 3 | +\n---[Example explanation of data significance to query.] +--- +Respond with the markdown table and an explanation of significance. Do not include extra text.`; diff --git a/packages/genaiscript/genaisrc/web-search.genai.mts b/packages/genaiscript/genaisrc/web-search.genai.mts new file mode 100644 index 0000000..d68a6e6 --- /dev/null +++ b/packages/genaiscript/genaisrc/web-search.genai.mts @@ -0,0 +1,28 @@ +import {SearxngClient} from "@agentic/searxng"; +import "./tools/searxng.genai.mjs" + + +script({ + title: "web_search_agent", + maxTokens: 8192, + cache: false, + tools: ["searxng"], +}); + + + +def("USER_INPUT", env.vars.user_input); + + + +def("LINK_FORMAT", "[Link](url)"); + +$`You are an assistant searching for web content using complex queries to pinpoint results. + + +- tailor search to answer the question in USER_INPUT +- perform 2 searches in parallel sorted by relevance and date respectively +- create a markdown table of <=5 results of both searches +- header row: Title, Description, and Link + +Respond with a single table, no extra text.` diff --git a/packages/genaiscript/package.json b/packages/genaiscript/package.json new file mode 100644 index 0000000..08a85d5 --- /dev/null +++ b/packages/genaiscript/package.json @@ -0,0 +1,28 @@ +{ + "name": "@web-agent-rs/genaiscript", + "type": "module", + "workspaces": ["packages/*"], + "private": true, + "scripts": { + "dev": "cargo watch -x 'run src/main.rs'", + "ai:search": "genaiscript run genaisrc/web-search.genai.mts --vars USER_INPUT='who won the 2024 election?'", + "shim:ai:search": "pnpm build && ./dist/shim.js --file=genaisrc/search.genai.mts USER_INPUT=\"Who won the 2024 presidential election?\"\n", + "ai:news": "genaiscript run genaisrc/news-search.genai.mts --vars USER_INPUT='What are the latest updates and developments in the Ukraine war?'", + "ai:url:read": "genaiscript run genaisrc/web-scrape.genai.mts --vars USER_INPUT='{\"url\":\"https://geoff.seemueller.io/about\",\"query\":\"Describe the details of the page.\", \"action\": \"read\"}'", + "ai:url:scrape": "npx genaiscript run genaisrc/web-scrape.genai.mts --vars USER_INPUT='{\"url\":\"https://www.time4learning.com/homeschool-curriculum/high-school/eleventh-grade/math.html\",\"query\":\"What is on this page?\", \"action\": \"scrape\"}'", + "crypto:quote": "npx genaiscript run genaisrc/finance-query.genai.mts --vars USER_INPUT='Get a quote for BTC'", + "crypto:news": "npx genaiscript run genaisrc/finance-query.genai.mts --vars USER_INPUT='What is the news for Bitcoin?'", + "crypto:overview": "npx genaiscript run genaisrc/finance-query.genai.mts --vars USER_INPUT='What are the trending symbols in the market?'" + }, + "dependencies": { + "@agentic/perigon": "^7.2.0", + "@agentic/searxng": "7.5.3", + "@kevinwatt/mcp-server-searxng": "^0.3.9", + "@types/node": "^22.10.2", + "genaiscript": "^1.95.1", + "happy-dom": "^16.0.1", + "@web-agent-rs/perigon": "workspace:*", + "@web-agent-rs/core": "workspace:*", + "ky": "^1.8.0" + } +} diff --git a/packages/perigon/index.ts b/packages/perigon/index.ts new file mode 100644 index 0000000..2bf58b1 --- /dev/null +++ b/packages/perigon/index.ts @@ -0,0 +1,106 @@ +import type * as types from './types'; +import type { ConfigOptions, FetchResponse } from 'api/dist/core' +import Oas from 'oas'; +import APICore from 'api/dist/core'; +import definition from './openapi.json'; + +class SDK { + spec: Oas; + core: APICore; + + constructor() { + this.spec = Oas.init(definition); + this.core = new APICore(this.spec, 'perigon/unknown (api/6.1.3)'); + } + + /** + * Optionally configure various options that the SDK allows. + * + * @param config Object of supported SDK options and toggles. + * @param config.timeout Override the default `fetch` request timeout of 30 seconds. This number + * should be represented in milliseconds. + */ + config(config: ConfigOptions) { + this.core.setConfig(config); + } + + /** + * If the API you're using requires authentication you can supply the required credentials + * through this method and the library will magically determine how they should be used + * within your API request. + * + * With the exception of OpenID and MutualTLS, it supports all forms of authentication + * supported by the OpenAPI specification. + * + * @example HTTP Basic auth + * sdk.auth('username', 'password'); + * + * @example Bearer tokens (HTTP or OAuth 2) + * sdk.auth('myBearerToken'); + * + * @example API Keys + * sdk.auth('myApiKey'); + * + * @see {@link https://spec.openapis.org/oas/v3.0.3#fixed-fields-22} + * @see {@link https://spec.openapis.org/oas/v3.1.0#fixed-fields-22} + * @param values Your auth credentials for the API; can specify up to two strings or numbers. + */ + auth(...values: string[] | number[]) { + this.core.setAuth(...values); + return this; + } + + /** + * If the API you're using offers alternate server URLs, and server variables, you can tell + * the SDK which one to use with this method. To use it you can supply either one of the + * server URLs that are contained within the OpenAPI definition (along with any server + * variables), or you can pass it a fully qualified URL to use (that may or may not exist + * within the OpenAPI definition). + * + * @example Server URL with server variables + * sdk.server('https://{region}.api.example.com/{basePath}', { + * name: 'eu', + * basePath: 'v14', + * }); + * + * @example Fully qualified server URL + * sdk.server('https://eu.api.example.com/v14'); + * + * @param url Server URL + * @param variables An object of variables to replace into the server URL. + */ + server(url: string, variables = {}) { + this.core.setServer(url, variables); + } + + /** + * Search and filter all news articles available via the Perigon API. The result includes a + * list of individual articles that were matched to your specific criteria. + * + * @summary All Articles + * @throws FetchError<400, types.AllNewsResponse400> 400 + * @throws FetchError<401, types.AllNewsResponse401> 401 + * @throws FetchError<403, types.AllNewsResponse403> 403 + * @throws FetchError<404, types.AllNewsResponse404> 404 + * @throws FetchError<500, types.AllNewsResponse500> 500 + */ + allNews(metadata: types.AllNewsMetadataParam): Promise> { + return this.core.fetch('/v1/all', 'get', metadata); + } + + /** + * Stories + * + * @throws FetchError<400, types.Stories1Response400> 400 + */ + stories1(metadata: types.Stories1MetadataParam): Promise> { + return this.core.fetch('/v1/stories/all', 'get', metadata); + } +} + +const createSDK = (() => { return new SDK(); })() +; + +export default createSDK; + +export type { AllNewsMetadataParam, AllNewsResponse200, AllNewsResponse400, AllNewsResponse401, AllNewsResponse403, AllNewsResponse404, AllNewsResponse500, Stories1MetadataParam, Stories1Response200, Stories1Response400 } from './types'; diff --git a/packages/perigon/openapi.json b/packages/perigon/openapi.json new file mode 100644 index 0000000..1e9ab15 --- /dev/null +++ b/packages/perigon/openapi.json @@ -0,0 +1,1629 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "News & Stories", + "version": "unknown" + }, + "servers": [ + { + "url": "https://api.goperigon.com" + } + ], + "components": { + "securitySchemes": { + "sec0": { + "type": "apiKey", + "in": "query", + "name": "apiKey", + "x-default": "" + } + } + }, + "security": [ + { + "sec0": [] + } + ], + "paths": { + "/v1/all": { + "get": { + "summary": "All Articles", + "description": "Search and filter all news articles available via the Perigon API. The result includes a list of individual articles that were matched to your specific criteria.", + "operationId": "all-news", + "parameters": [ + { + "name": "apiKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "q", + "in": "query", + "description": "Search query, each article will be scored and ranked against it. Articles are searched on the title, description, and content fields. More ➜", + "schema": { + "type": "string", + "default": "recall OR \"safety concern*\"" + } + }, + { + "name": "title", + "in": "query", + "description": "Search article headlines/title field. Semantic similar to q parameter.", + "schema": { + "type": "string", + "default": "tesla OR TSLA OR \"General Motors\" OR GM" + } + }, + { + "name": "desc", + "in": "query", + "description": "Search query on the description field. Semantic similar to q parameter.", + "schema": { + "type": "string" + } + }, + { + "name": "content", + "in": "query", + "description": "Search query on the article's body of content field. Semantic similar to q parameter.", + "schema": { + "type": "string" + } + }, + { + "name": "url", + "in": "query", + "description": "Search query on the url field. Semantic similar to q parameter. E.g. could be used for querying certain website sections, e.g. source=cnn.com&url=travel.", + "schema": { + "type": "string" + } + }, + { + "name": "articleId", + "in": "query", + "description": "Article ID will search for a news article by the ID of the article. If several parameters are passed, all matched articles will be returned.", + "schema": { + "type": "string" + } + }, + { + "name": "clusterId", + "in": "query", + "description": "Search for related content using a cluster ID. Passing a cluster ID will filter results to only the content found within the cluster.", + "schema": { + "type": "string" + } + }, + { + "name": "from", + "in": "query", + "description": "'from' filter, will search articles published after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "description": "'to' filter, will search articles published before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T23:59:59", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "addDateFrom", + "in": "query", + "description": "'addDateFrom' filter, will search articles added after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T00:00:00", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "addDateTo", + "in": "query", + "description": "'addDateTo' filter, will search articles added before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T23:59:59", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "refreshDateFrom", + "in": "query", + "description": "Will search articles that were refreshed after the specified date. The date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T00:00:00", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "refreshDateTo", + "in": "query", + "description": "Will search articles that were refreshed before the specified date. The date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T23:59:59", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "medium", + "in": "query", + "description": "Medium will filter out news articles medium, which could be 'Video' or 'Article'. If several parameters are passed, all matched articles will be returned.", + "schema": { + "type": "string", + "enum": [ + "Article", + "Video" + ] + } + }, + { + "name": "source", + "in": "query", + "description": "Publisher's domain can include a subdomain. If multiple parameters are passed, they will be applied as OR operations. Wildcards (\\* and ?) are suported (e.g. \\*.cnn.com). More ➜", + "schema": { + "type": "string" + } + }, + { + "name": "sourceGroup", + "in": "query", + "description": "One of the supported source groups. Find Source Groups in the guided part of our documentation... More ➜", + "schema": { + "type": "string", + "enum": [ + "top10", + "top100", + "top25crypto", + "top25finance", + "top50tech", + "top100sports", + "top100leftUS", + "top100rightUS", + "top100centerUS" + ] + } + }, + { + "name": "excludeSource", + "in": "query", + "description": "The domain of the website, which should be excluded from the search. Multiple parameters could be provided. Wildcards (\\* and ?) are suported (e.g. \\*.cnn.com).", + "schema": { + "type": "string" + } + }, + { + "name": "paywall", + "in": "query", + "description": "Filter to show only results where the source has a paywall (true) or does not have a paywall (false).", + "schema": { + "type": "boolean" + } + }, + { + "name": "byline", + "in": "query", + "description": "Author names to filter by. Article author bylines are used as a source field. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "string" + } + }, + { + "name": "journalistId", + "in": "query", + "description": "Filter by journalist ID. Journalist IDs are unique journalist identifiers which can be found through the Journalist API, or in the matchedAuthors field. More ➜", + "schema": { + "type": "string" + } + }, + { + "name": "language", + "in": "query", + "description": "Language code to filter by language. If multiple parameters are passed, they will be applied as OR operations. More ➜", + "schema": { + "type": "string", + "enum": [ + "da", + "de", + "en", + "es", + "fi", + "fr", + "hu", + "it", + "nl", + "no", + "pl", + "pt", + "ru", + "sv", + "uk" + ] + } + }, + { + "name": "searchTranslation", + "in": "query", + "description": "Expand a query to search the translation, translatedTitle, and translatedDescription fields for non-English articles.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "label", + "in": "query", + "description": "Labels to filter by, could be 'Opinion', 'Paid-news', 'Non-news', etc. If multiple parameters are passed, they will be applied as OR operations. More ➜", + "schema": { + "type": "string", + "enum": [ + "Opinion", + "Non-news", + "Paid News", + "Fact Check", + "Pop Culture", + "Roundup", + "Press Release", + "Low Content" + ] + } + }, + { + "name": "excludeLabel", + "in": "query", + "description": "Exclude results that include specific labels (Opinion, Non-news, Paid News, etc.). You can filter multiple by repeating the parameter.", + "schema": { + "type": "string", + "enum": [ + "Opinion", + "Non-news", + "Paid News", + "Fact Check", + "Pop Culture", + "Roundup", + "Press Release", + "Low Content" + ] + } + }, + { + "name": "category", + "in": "query", + "description": "Filter by categories. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. Use 'none' to search uncategorized articles. More ➜", + "schema": { + "type": "string", + "enum": [ + "Politics", + "Tech", + "Sports", + "Business", + "Finance", + "Entertainment", + "Health", + "Weather", + "Lifestyle", + "Auto", + "Science", + "Travel", + "Environment", + "World", + "General", + "none" + ] + } + }, + { + "name": "topic", + "in": "query", + "description": "Filter by topics. Each topic is some kind of entity that the article is about. Examples of topics: Markets, Joe Biden, Green Energy, Climate Change, Cryptocurrency, etc. If multiple parameters are passed, they will be applied as OR operations. More ➜", + "schema": { + "type": "string" + } + }, + { + "name": "excludeTopic", + "in": "query", + "description": "Filter by excluding topics. Each topic is some kind of entity that the article is about. Examples of topics: Markets, Joe Biden, Green Energy, Climate Change, Cryptocurrency, etc. If multiple parameters are passed, they will be applied as OR operations. More ➜", + "schema": { + "type": "string" + } + }, + { + "name": "linkTo", + "in": "query", + "description": "Returns only articles that point to specified links (as determined by the 'links' field in the article response).", + "schema": { + "type": "string" + } + }, + { + "name": "showReprints", + "in": "query", + "description": "Whether to return reprints in the response or not. Reprints are usually wired articles from sources like AP or Reuters that are reprinted in multiple sources at the same time. By default, this parameter is 'true'.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "reprintGroupId", + "in": "query", + "description": "Shows all articles belonging to the same reprint group. A reprint group includes one original article (the first one processed by the API) and all its known reprints.", + "schema": { + "type": "string" + } + }, + { + "name": "city", + "in": "query", + "description": "Filters articles where a specified city plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the urban area in question. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "string" + } + }, + { + "name": "area", + "in": "query", + "description": "Filters articles where a specified area, such as a neighborhood, borough, or district, plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the area in question. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "description": "Filters articles where a specified state plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the state in question. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "string" + } + }, + { + "name": "locationsCountry", + "in": "query", + "description": "Filters articles where a specified country plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the country in question. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "string" + } + }, + { + "name": "excludeLocationsCountry", + "in": "query", + "description": "Excludes articles where a specified country plays a central role in the content, ensuring results are not deeply relevant to the country in question. If multiple parameters are passed, they will be applied as AND operations, excluding articles relevant to any of the specified countries.", + "schema": { + "type": "string" + } + }, + { + "name": "location", + "in": "query", + "description": "Return all articles that have the specified location. Location attributes are delimited by ':' between key and value, and '::' between attributes. Example: 'city:New York::state:NY'.", + "schema": { + "type": "string" + } + }, + { + "name": "lat", + "in": "query", + "description": "Latitude of the center point to search places", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "lon", + "in": "query", + "description": "Longitude of the center point to search places", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxDistance", + "in": "query", + "description": "Maximum distance (in km) from starting point to search articles by tagged places", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "sourceCity", + "in": "query", + "description": "Find articles published by sources that are located within a given city.", + "schema": { + "type": "string" + } + }, + { + "name": "sourceCounty", + "in": "query", + "description": "Find articles published by sources that are located within a given county.", + "schema": { + "type": "string" + } + }, + { + "name": "sourceCountry", + "in": "query", + "description": "Find articles published by sources that are located within a given country. Must be 2 character country code (i.e. us, gb, etc).", + "schema": { + "type": "string" + } + }, + { + "name": "country", + "in": "query", + "description": "Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "string", + "enum": [ + "us", + "gb", + "de", + "it", + "fr", + "nl", + "se", + "dk", + "fi", + "hu", + "no", + "pl", + "pt", + "ru", + "ua", + "ch", + "br", + "nz", + "mx", + "au" + ] + } + }, + { + "name": "sourceState", + "in": "query", + "description": "Find articles published by sources that are located within a given state.", + "schema": { + "type": "string" + } + }, + { + "name": "sourceLon", + "in": "query", + "description": "Latitude of the center point to search articles created by local publications.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sourceLat", + "in": "query", + "description": "Latitude of the center point to search articles created by local publications.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "sourceMaxDistance", + "in": "query", + "description": "Maximum distance from starting point to search articles created by local publications.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "personWikidataId", + "in": "query", + "description": "List of person Wikidata IDs for filtering.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "personName", + "in": "query", + "description": "List of person names for exact matches. Boolean and complex logic is not supported on this paramter.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "companyId", + "in": "query", + "description": "List of company IDs to filter by.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "companyName", + "in": "query", + "description": "Search by company name.", + "schema": { + "type": "string" + } + }, + { + "name": "companyDomain", + "in": "query", + "description": "Search by company domains for filtering. E.g. companyDomain=apple.com.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "companySymbol", + "in": "query", + "description": "Search by company symbols.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "page", + "in": "query", + "description": "Zero-based page number. From 0 to 10000. See the Pagination section for limitations.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "size", + "in": "query", + "description": "Number of articles returned per page, from 0 to 100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "'relevance' to sort by relevance to the query, 'date' to sort by the publication date (desc), 'pubDate' is a synonym to 'date', 'addDate' to sort by 'addDate' field (desc), 'refreshDate' to sort by 'refreshDate' field (desc).", + "schema": { + "type": "string", + "enum": [ + "date", + "relevance", + "addDate", + "pubDate", + "refreshDate" + ], + "default": "relevance" + } + }, + { + "name": "showNumResults", + "in": "query", + "description": "Whether to show the total number of all matched articles. Default value is false which makes queries a bit more efficient but also counts up to 10000 articles.", + "schema": { + "type": "boolean" + } + }, + { + "name": "positiveSentimentFrom", + "in": "query", + "description": "Filters results with a sentiment score greater than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "positiveSentimentTo", + "in": "query", + "description": "Filters results with a sentiment score less than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "neutralSentimentFrom", + "in": "query", + "description": "Filters results with a sentiment score greater than or equal to the specified value, indicating neutral sentiment. Explanation of sentimental values can be found in Docs under the Article Data section.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "neutralSentimentTo", + "in": "query", + "description": "Filters results with a sentiment score less than or equal to the specified value, indicating neutral sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "negativeSentimentFrom", + "in": "query", + "description": "Filters results with a sentiment score greater than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "negativeSentimentTo", + "in": "query", + "description": "Filters results with a sentiment score less than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "taxonomy", + "in": "query", + "description": "Filters by Google Content Categories. This field will accept 1 or more categories, must pass the full name of the category. Example: taxonomy=/Finance/Banking/Other, /Finance/Investing/Funds", + "schema": { + "type": "string" + } + }, + { + "name": "prefixTaxonomy", + "in": "query", + "description": "Filters by Google Content Categories. This field will filter by the category prefix only. Example: prefixTaxonomy=/Finance", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "200", + "content": { + "application/json": { + "examples": { + "Result": { + "value": "{\n \"status\": 200,\n \"numResults\": 1,\n \"articles\": [\n {\n \"url\": \"https://decrypt.co/301053/nft-market-hits-three-year-low-in-trading-and-sales-report\",\n \"authorsByline\": \"Vismaya V\",\n \"articleId\": \"680d7893185c4357a9047332cf38206c\",\n \"clusterId\": \"37426b0543cb45e3a547695fd63e9919\",\n \"source\": {\n \"domain\": \"decrypt.co\",\n \"paywall\": false,\n \"location\": {\n \"country\": \"us\",\n \"state\": \"NY\",\n \"city\": \"New York\",\n \"coordinates\": {\n \"lat\": 40.7127281,\n \"lon\": -74.0060152\n }\n }\n },\n \"imageUrl\": \"https://cdn.decrypt.co/resize/1024/height/512/wp-content/uploads/2021/07/The-Bored-Ape-Yacht-Club-gID_7.jpeg\",\n \"country\": \"us\",\n \"language\": \"en\",\n \"pubDate\": \"2025-01-15T09:20:34+00:00\",\n \"addDate\": \"2025-01-15T09:27:57.156032+00:00\",\n \"refreshDate\": \"2025-01-15T09:27:57.156034+00:00\",\n \"score\": 52.79721,\n \"title\": \"NFT Market Hits Three-Year Low in Trading and Sales: Report\",\n \"description\": \"Annual NFT trading volumes fell by 19%, while sales counts dipped by 18% compared to 2023, according to DappRadar.\",\n \"content\": \"The NFT market suffered a dismal 2024, with trading volumes and sales counts dropping to their weakest levels since 2020.\\n\\nAnnual trading volumes fell by 19%, while sales counts dipped by 18% compared to 2023, according to a report by blockchain analytics platform.\\n\\nDespite a surge in crypto market activity, driven by Bitcoin’s all-time highs and booming DeFi growth, NFTs appeared to struggle under the weight of their own inflated valuations.\\n\\nEarly in the year, NFT trading volumes reached $5.3 billion in Q1, a modest 4% increase compared to the same period in 2023.\\n\\nHowever, this momentum proved fleeting, as volumes plummeted to $1.5 billion in Q3 before recovering slightly to $2.6 billion in Q4.\\n\\nEven with these fluctuations, annual sales counts fell sharply, pointing to a broader trend: while individual NFTs became more expensive in line with rising crypto token prices, overall market engagement dwindled.\\n\\nYuga Labs’ flagship collections Bored Ape Yacht Club (BAYC) and Mutant Ape Yacht Club (MAYC) hit historic lows, with floor prices dropping to 15 ETH and 2.4 ETH, respectively.\\n\\nEven Otherdeeds for Yuga Labs' Otherside metaverse plummeted to 0.23 ETH, a far cry from their initial minting price, exposing cracks in Yuga’s high-priced, membership-driven model.\\n\\nThis coincided with DappRadar’s observation that “Perhaps 2024 helped us realize that NFTs don’t need to be expensive to prove their importance in the broader Web3 ecosystem,” a critique of the market’s reliance on exclusivity and inflated pricing.\\n\\nAmid this downturn, the NFT market witnessed a paradox in November when CryptoPunk #8348, a rare seven-trait collectible from the NFT collection, was collateralized for a $2.75 million loan via the NFT lending platform GONDI.\\n\\nTouted as a milestone for NFTs as financial assets, this event showed speculative excess when juxtaposed with DappRadar’s insights about affordability and utility.\\n\\nWhile high-profile transactions like this aim to affirm NFTs’ value, they also highlight a market still driven by exclusivity and inflated pricing, even as wider participation wanes.\\n\\nEven within the struggling sector, blue-chip collections like CryptoPunks defied trends, nearly doubling in USD value in 2024 with notable sales driving brief recovery periods.\\n\\nNFT platforms like Blur dominated marketplace activity, leveraging zero-fee trading and aggressive airdrop campaigns to capture the largest share of trading volumes.\\n\\nIn contrast, rival marketplace OpenSea struggled with regulatory headwinds and declining market sentiment, forcing significant layoffs by year-end.\\n\\nBy Q4, Blur and OpenSea were neck-and-neck in market share, but Blur’s ability to generate high activity from a smaller, more active user base gave it the edge, as per the report.\\n\\nWhile trading volumes in late 2024 hinted at a potential recovery—November sales hit $562 million, the highest since May—the overall trajectory suggests that affordability, accessibility, and utility will be critical for sustained growth in 2025.\",\n \"medium\": \"Article\",\n \"links\": [\n \"https://dappradar.com/blog/dapp-industry-report-2024-overview\",\n \"https://decrypt.co/resources/what-is-the-metaverse-immersive-nft-virtual-world\",\n \"https://decrypt.co/resources/non-fungible-tokens-nfts-explained-guide-learn-blockchain\",\n \"https://decrypt.co/247932/sec-coming-after-opensea-nfts-could-be-in-trouble\",\n \"https://share.flipboard.com/bookmarklet/popout?v=2&url=https://decrypt.co/301053/nft-market-hits-three-year-low-in-trading-and-sales-report\",\n \"https://decrypt.co/resources/what-are-cryptopunks-ethereum-nft-avatars\",\n \"https://decrypt.co/291710/cryptopunk-prices-surge-bitcoin-gains\",\n \"https://decrypt.co/scene\",\n \"https://decrypt.co/resources/what-is-bored-ape-yacht-club-the-celebrity-nft-of-choice\",\n \"https://decrypt.co/247930/if-you-bought-bored-ape-nfts-at-the-peak-youve-lost-93-of-your-investment\",\n \"https://decrypt.co/293754/someone-just-took-out-a-2-75-million-loan-against-this-one-of-one-cryptopunk-nft\"\n ],\n \"labels\": [\n {\n \"name\": \"Roundup\"\n }\n ],\n \"matchedAuthors\": [\n {\n \"id\": null,\n \"name\": \"Vismaya V\"\n }\n ],\n \"claim\": \"\",\n \"verdict\": \"\",\n \"keywords\": [\n {\n \"name\": \"NFT trading volumes\",\n \"weight\": 0.095650345\n },\n {\n \"name\": \"NFT Market\",\n \"weight\": 0.09479949\n },\n {\n \"name\": \"crypto market activity\",\n \"weight\": 0.09068515\n },\n {\n \"name\": \"Annual trading volumes\",\n \"weight\": 0.08782178\n },\n {\n \"name\": \"trading volumes\",\n \"weight\": 0.087688774\n },\n {\n \"name\": \"market share\",\n \"weight\": 0.08508381\n },\n {\n \"name\": \"overall market engagement\",\n \"weight\": 0.08292204\n },\n {\n \"name\": \"declining market sentiment\",\n \"weight\": 0.080258906\n },\n {\n \"name\": \"annual sales counts\",\n \"weight\": 0.07653589\n },\n {\n \"name\": \"sales counts\",\n \"weight\": 0.07410056\n }\n ],\n \"topics\": [],\n \"categories\": [],\n \"taxonomies\": [\n {\n \"name\": \"/Finance/Investing/Currencies & Foreign Exchange\"\n },\n {\n \"name\": \"/News/Business News/Financial Markets News\"\n }\n ],\n \"entities\": [\n {\n \"data\": \"Yuga Labs’\",\n \"type\": \"ORG\",\n \"mentions\": 3\n },\n {\n \"data\": \"Bored Ape Yacht Club\",\n \"type\": \"ORG\",\n \"mentions\": 1\n },\n {\n \"data\": \"BAYC\",\n \"type\": \"ORG\",\n \"mentions\": 1\n },\n {\n \"data\": \"Mutant Ape Yacht Club\",\n \"type\": \"ORG\",\n \"mentions\": 1\n },\n {\n \"data\": \"MAYC\",\n \"type\": \"ORG\",\n \"mentions\": 1\n },\n {\n \"data\": \"DappRadar\",\n \"type\": \"ORG\",\n \"mentions\": 2\n },\n {\n \"data\": \"NFT\",\n \"type\": \"ORG\",\n \"mentions\": 4\n },\n {\n \"data\": \"GONDI\",\n \"type\": \"ORG\",\n \"mentions\": 1\n },\n {\n \"data\": \"Blur\",\n \"type\": \"ORG\",\n \"mentions\": 3\n },\n {\n \"data\": \"OpenSea\",\n \"type\": \"ORG\",\n \"mentions\": 2\n },\n {\n \"data\": \"Otherside\",\n \"type\": \"PRODUCT\",\n \"mentions\": 1\n },\n {\n \"data\": \"CryptoPunks\",\n \"type\": \"PRODUCT\",\n \"mentions\": 1\n }\n ],\n \"companies\": [\n {\n \"id\": \"f0886b1323a142df88f521ba9a0bbedf\",\n \"name\": \"OpenSea Ventures\",\n \"domains\": [\n \"opensea.io\"\n ],\n \"symbols\": []\n },\n {\n \"id\": \"bee2238a04fe4f228252ea641c3403a7\",\n \"name\": \"NFT Labs\",\n \"domains\": [\n \"nftlabs.to\"\n ],\n \"symbols\": []\n }\n ],\n \"sentiment\": {\n \"positive\": 0.033333376,\n \"negative\": 0.89130986,\n \"neutral\": 0.07535672\n },\n \"summary\": \"The National Finance Finance (NFT) market experienced a three-year low in 2024, with annual trading volumes and sales counts dropping to their lowest levels since 2020. The report by blockchain analytics platform DappRadar suggests that despite a surge in crypto market activity driven by Bitcoin's all-time highs and DeFi growth, NFTs struggled under the weight of their inflated valuations. Despite early Q1 trading volumes reaching $5.3 billion, a 4% increase compared to the same period in 2023, volumes dropped to $1.5 billion in Q3 before recovering to $2.6 billion by Q4. Despite these fluctuations, overall market engagement decreased. Despite this downturn, high-profile transactions like CryptoPunk #8348, a rare seven-trait collectible from the NFT collection, were seen as a milestone for NFT's value and highlighted a market still driven by exclusivity and inflated pricing.\",\n \"translation\": \"\",\n \"translatedTitle\": \"\",\n \"translatedDescription\": \"\",\n \"translatedSummary\": \"\",\n \"locations\": [],\n \"reprint\": false,\n \"reprintGroupId\": \"3d67e5bf389b40b3895ae0708f4dd208\",\n \"places\": [],\n \"people\": []\n }\n ]\n}" + } + }, + "schema": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "example": 200, + "default": 0 + }, + "numResults": { + "type": "integer", + "example": 1, + "default": 0 + }, + "articles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "example": "https://decrypt.co/301053/nft-market-hits-three-year-low-in-trading-and-sales-report" + }, + "authorsByline": { + "type": "string", + "example": "Vismaya V" + }, + "articleId": { + "type": "string", + "example": "680d7893185c4357a9047332cf38206c" + }, + "clusterId": { + "type": "string", + "example": "37426b0543cb45e3a547695fd63e9919" + }, + "source": { + "type": "object", + "properties": { + "domain": { + "type": "string", + "example": "decrypt.co" + }, + "paywall": { + "type": "boolean", + "example": false, + "default": true + }, + "location": { + "type": "object", + "properties": { + "country": { + "type": "string", + "example": "us" + }, + "state": { + "type": "string", + "example": "NY" + }, + "city": { + "type": "string", + "example": "New York" + }, + "coordinates": { + "type": "object", + "properties": { + "lat": { + "type": "number", + "example": 40.7127281, + "default": 0 + }, + "lon": { + "type": "number", + "example": -74.0060152, + "default": 0 + } + } + } + } + } + } + }, + "imageUrl": { + "type": "string", + "example": "https://cdn.decrypt.co/resize/1024/height/512/wp-content/uploads/2021/07/The-Bored-Ape-Yacht-Club-gID_7.jpeg" + }, + "country": { + "type": "string", + "example": "us" + }, + "language": { + "type": "string", + "example": "en" + }, + "pubDate": { + "type": "string", + "example": "2025-01-15T09:20:34+00:00" + }, + "addDate": { + "type": "string", + "example": "2025-01-15T09:27:57.156032+00:00" + }, + "refreshDate": { + "type": "string", + "example": "2025-01-15T09:27:57.156034+00:00" + }, + "score": { + "type": "number", + "example": 52.79721, + "default": 0 + }, + "title": { + "type": "string", + "example": "NFT Market Hits Three-Year Low in Trading and Sales: Report" + }, + "description": { + "type": "string", + "example": "Annual NFT trading volumes fell by 19%, while sales counts dipped by 18% compared to 2023, according to DappRadar." + }, + "content": { + "type": "string", + "example": "The NFT market suffered a dismal 2024, with trading volumes and sales counts dropping to their weakest levels since 2020.\n\nAnnual trading volumes fell by 19%, while sales counts dipped by 18% compared to 2023, according to a report by blockchain analytics platform.\n\nDespite a surge in crypto market activity, driven by Bitcoin’s all-time highs and booming DeFi growth, NFTs appeared to struggle under the weight of their own inflated valuations.\n\nEarly in the year, NFT trading volumes reached $5.3 billion in Q1, a modest 4% increase compared to the same period in 2023.\n\nHowever, this momentum proved fleeting, as volumes plummeted to $1.5 billion in Q3 before recovering slightly to $2.6 billion in Q4.\n\nEven with these fluctuations, annual sales counts fell sharply, pointing to a broader trend: while individual NFTs became more expensive in line with rising crypto token prices, overall market engagement dwindled.\n\nYuga Labs’ flagship collections Bored Ape Yacht Club (BAYC) and Mutant Ape Yacht Club (MAYC) hit historic lows, with floor prices dropping to 15 ETH and 2.4 ETH, respectively.\n\nEven Otherdeeds for Yuga Labs' Otherside metaverse plummeted to 0.23 ETH, a far cry from their initial minting price, exposing cracks in Yuga’s high-priced, membership-driven model.\n\nThis coincided with DappRadar’s observation that “Perhaps 2024 helped us realize that NFTs don’t need to be expensive to prove their importance in the broader Web3 ecosystem,” a critique of the market’s reliance on exclusivity and inflated pricing.\n\nAmid this downturn, the NFT market witnessed a paradox in November when CryptoPunk #8348, a rare seven-trait collectible from the NFT collection, was collateralized for a $2.75 million loan via the NFT lending platform GONDI.\n\nTouted as a milestone for NFTs as financial assets, this event showed speculative excess when juxtaposed with DappRadar’s insights about affordability and utility.\n\nWhile high-profile transactions like this aim to affirm NFTs’ value, they also highlight a market still driven by exclusivity and inflated pricing, even as wider participation wanes.\n\nEven within the struggling sector, blue-chip collections like CryptoPunks defied trends, nearly doubling in USD value in 2024 with notable sales driving brief recovery periods.\n\nNFT platforms like Blur dominated marketplace activity, leveraging zero-fee trading and aggressive airdrop campaigns to capture the largest share of trading volumes.\n\nIn contrast, rival marketplace OpenSea struggled with regulatory headwinds and declining market sentiment, forcing significant layoffs by year-end.\n\nBy Q4, Blur and OpenSea were neck-and-neck in market share, but Blur’s ability to generate high activity from a smaller, more active user base gave it the edge, as per the report.\n\nWhile trading volumes in late 2024 hinted at a potential recovery—November sales hit $562 million, the highest since May—the overall trajectory suggests that affordability, accessibility, and utility will be critical for sustained growth in 2025." + }, + "medium": { + "type": "string", + "example": "Article" + }, + "links": { + "type": "array", + "items": { + "type": "string", + "example": "https://dappradar.com/blog/dapp-industry-report-2024-overview" + } + }, + "labels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "Roundup" + } + } + } + }, + "matchedAuthors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": {}, + "name": { + "type": "string", + "example": "Vismaya V" + } + } + } + }, + "claim": { + "type": "string", + "example": "" + }, + "verdict": { + "type": "string", + "example": "" + }, + "keywords": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "NFT trading volumes" + }, + "weight": { + "type": "number", + "example": 0.095650345, + "default": 0 + } + } + } + }, + "topics": { + "type": "array" + }, + "categories": { + "type": "array" + }, + "taxonomies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "/Finance/Investing/Currencies & Foreign Exchange" + } + } + } + }, + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "data": { + "type": "string", + "example": "Yuga Labs’" + }, + "type": { + "type": "string", + "example": "ORG" + }, + "mentions": { + "type": "integer", + "example": 3, + "default": 0 + } + } + } + }, + "companies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "f0886b1323a142df88f521ba9a0bbedf" + }, + "name": { + "type": "string", + "example": "OpenSea Ventures" + }, + "domains": { + "type": "array", + "items": { + "type": "string", + "example": "opensea.io" + } + }, + "symbols": { + "type": "array" + } + } + } + }, + "sentiment": { + "type": "object", + "properties": { + "positive": { + "type": "number", + "example": 0.033333376, + "default": 0 + }, + "negative": { + "type": "number", + "example": 0.89130986, + "default": 0 + }, + "neutral": { + "type": "number", + "example": 0.07535672, + "default": 0 + } + } + }, + "summary": { + "type": "string", + "example": "The National Finance Finance (NFT) market experienced a three-year low in 2024, with annual trading volumes and sales counts dropping to their lowest levels since 2020. The report by blockchain analytics platform DappRadar suggests that despite a surge in crypto market activity driven by Bitcoin's all-time highs and DeFi growth, NFTs struggled under the weight of their inflated valuations. Despite early Q1 trading volumes reaching $5.3 billion, a 4% increase compared to the same period in 2023, volumes dropped to $1.5 billion in Q3 before recovering to $2.6 billion by Q4. Despite these fluctuations, overall market engagement decreased. Despite this downturn, high-profile transactions like CryptoPunk #8348, a rare seven-trait collectible from the NFT collection, were seen as a milestone for NFT's value and highlighted a market still driven by exclusivity and inflated pricing." + }, + "translation": { + "type": "string", + "example": "" + }, + "translatedTitle": { + "type": "string", + "example": "" + }, + "translatedDescription": { + "type": "string", + "example": "" + }, + "translatedSummary": { + "type": "string", + "example": "" + }, + "locations": { + "type": "array" + }, + "reprint": { + "type": "boolean", + "example": false, + "default": true + }, + "reprintGroupId": { + "type": "string", + "example": "3d67e5bf389b40b3895ae0708f4dd208" + }, + "places": { + "type": "array" + }, + "people": { + "type": "array" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "400", + "content": { + "text/plain": { + "examples": { + "Result": { + "value": "The request has bad formatting or missing parameters" + } + } + } + } + }, + "401": { + "description": "401", + "content": { + "text/plain": { + "examples": { + "Result": { + "value": "No API key was provided in the request" + } + } + } + } + }, + "403": { + "description": "403", + "content": { + "text/plain": { + "examples": { + "Result": { + "value": "Request was forbidden, likely due to plan restrictions or account status" + } + } + } + } + }, + "404": { + "description": "404", + "content": { + "text/plain": { + "examples": { + "Result": { + "value": "The requested resource was not found" + } + } + } + } + }, + "500": { + "description": "500", + "content": { + "text/plain": { + "examples": { + "Result": { + "value": "Some internal error has occurred, please contact support" + } + } + } + } + } + }, + "deprecated": false, + "security": [], + "x-readme": { + "code-samples": [ + { + "language": "curl", + "code": "$ curl 'https://api.goperigon.com/v1/all?category=Business&sourceGroup=top100&showReprints=false&apiKey=API_KEY'" + }, + { + "language": "python", + "code": "import requests\n\nAPI_KEY = \"API_KEY\"\nurl = f\"https://api.goperigon.com/v1/all?category=Business&sourceGroup=top100&showReprints=false&apiKey={API_KEY}\"\n\nresp = requests.get(url)\n\nprint(resp.json())" + }, + { + "language": "ruby", + "code": "require 'net/http'\n\napi_key = \"API_KEY\"\nurl = URI.parse(\"https://api.goperigon.com/v1/all?category=Business&sourceGroup=top100&showReprints=false&apiKey=#{api_key}\")\nres = Net::HTTP.get(URI.parse(url.to_s))\n\nputs res" + }, + { + "language": "go", + "code": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n key := \"API_KEY\"\n url := \"https://api.goperigon.com/v1/all?category=Business&sourceGroup=top100&showReprints=false&apiKey=\" + key \n res, err := http.Get(url)\n if err != nil {\n fmt.Println(err)\n }\n \n defer res.Body.Close()\n body, err := ioutil.ReadAll(res.Body)\n if err != nil {\n fmt.Println(err)\n }\n \n fmt.Println(string(body))\n}" + }, + { + "language": "javascript", + "code": "const axios = require('axios');\nconst apiKey = \"API_KEY\"\nconst url = `https://api.goperigon.com/v1/all?category=Business&sourceGroup=top100&showReprints=false&apiKey=${apiKey}`\n\naxios.get(url)\n .then((response) => {\n console.log(response);\n })\n .catch((error) => {\n console.log(error);\n });" + } + ], + "samples-languages": [ + "curl", + "python", + "ruby", + "go", + "javascript" + ] + } + } + }, + "/v1/stories/all": { + "get": { + "summary": "Stories", + "description": "", + "operationId": "stories-1", + "parameters": [ + { + "name": "apiKey", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "q", + "in": "query", + "description": "Search story by name, summary and key points. Preference is given to the name field. Supports complex query syntax, same way as **q** parameter from **/all** endpoint.", + "schema": { + "type": "string" + } + }, + { + "name": "name", + "in": "query", + "description": "Search story by name. Supports complex query syntax, same way as **q** parameter from **/all** endpoint.", + "schema": { + "type": "string" + } + }, + { + "name": "topic", + "in": "query", + "description": "Filter by topics. Each topic is some kind of entity that the article is about. Examples of topics: Markets, Joe Biden, Green Energy, Climate Change, Cryptocurrency, etc. If multiple parameters are passed, they will be applied as OR operations. More ➜", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "category", + "in": "query", + "description": "Filter by categories. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. Use 'none' to search uncategorized articles. More ➜", + "schema": { + "type": "string", + "enum": [ + "Politics", + "Tech", + "Sports", + "Business", + "Finance", + "Entertainment", + "Health", + "Weather", + "Lifestyle", + "Auto", + "Science", + "Travel", + "Environment", + "World", + "General", + "none" + ] + } + }, + { + "name": "clusterId", + "in": "query", + "description": "Filter to specific story. Passing a cluster ID will filter results to only the content found within the cluster. Multiple params could be passed.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "source", + "in": "query", + "description": "Filter stories by sources that wrote articles belonging to this story. At least 1 article is required for story to match. Multiple parameters could be passed.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "sourceGroup", + "in": "query", + "description": "Filter stories by sources that wrote articles belonging to this story. Source groups are expanded into a list of sources. At least 1 article by the source is required for story to match. Multiple params could be passed.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "personWikidataId", + "in": "query", + "description": "List of person Wikidata IDs for filtering. Filter is applied on topPeople field.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "personName", + "in": "query", + "description": "List of people names. Filtering is applied on topPeople field.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "companyId", + "in": "query", + "description": "List of company IDs for filtering. Filtering is applied to topCompanies field.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "companyName", + "in": "query", + "description": "List of company names for filtering. Filtering is applied on topCompanies field.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "companyDomain", + "in": "query", + "description": "List of company domains for filtering. Filtering is applied on topCompanies field.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "companySymbol", + "in": "query", + "description": "List of company tickers for filtering. Filtering is applied on topCompanies field.", + "schema": { + "type": "string" + } + }, + { + "name": "nameExists", + "in": "query", + "description": "Returns stories with name assigned. Defaults to true.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "from", + "in": "query", + "description": "'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "to", + "in": "query", + "description": "'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "initializedFrom", + "in": "query", + "description": "'initializedFrom' filter, will search stories that became available after provided date", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "initializedTo", + "in": "query", + "description": "'initializedTo' filter, will search stories that became available before provided date", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "updatedFrom", + "in": "query", + "description": "Will return stories with 'updatedAt' >= 'updatedFrom'.", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "updatedTo", + "in": "query", + "description": "Will return stories with 'updatedAt' <= 'updatedTo'.", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "minClusterSize", + "in": "query", + "description": "Filter by minimum cluster size. Minimum cluster size filter applies to number of **unique** articles.", + "schema": { + "type": "integer", + "format": "int32", + "default": 5 + } + }, + { + "name": "maxClusterSize", + "in": "query", + "description": "Filter by maximum cluster size. Maximum cluster size filter applies to number of **unique** articles in the cluster.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "state", + "in": "query", + "description": "Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "city", + "in": "query", + "description": "Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations. More ➜", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "area", + "in": "query", + "description": "Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "page", + "in": "query", + "description": "Zero-based page number. From 0 to 10000. See the Pagination section for limitations.", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "size", + "in": "query", + "description": "Number of stories results per page, from 0 to 100.", + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Sort stories by count, creation date, last updated date. By default is sorted by created at.", + "schema": { + "type": "string", + "enum": [ + "createdAt", + "updatedAt", + "count" + ], + "default": "createdAt" + } + }, + { + "name": "showNumResults", + "in": "query", + "description": "Show total number of results. By default set to false, will cap result count at 10000.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "showDuplicates", + "in": "query", + "description": "Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. *duplicateOf* field contains the original cluster Id. When *showDuplicates=true*, all stories are shown.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "positiveSentimentFrom", + "in": "query", + "description": "Filters results with a sentiment score greater than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "positiveSentimentTo", + "in": "query", + "description": "Filters results with a sentiment score less than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "neutralSentimentFrom", + "in": "query", + "description": "Filters results with a sentiment score greater than or equal to the specified value, indicating neutral sentiment. Explanation of sentimental values can be found in Docs under the Article Data section.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "neutralSentimentTo", + "in": "query", + "description": "Filters results with a sentiment score less than or equal to the specified value, indicating neutral sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "negativeSentimentFrom", + "in": "query", + "description": "Filters results with a sentiment score greater than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "negativeSentimentTo", + "in": "query", + "description": "Filters results with a sentiment score less than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "country", + "in": "query", + "description": "Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations.", + "schema": { + "type": "string", + "enum": [ + "us", + "gb", + "de", + "it", + "fr", + "nl", + "se", + "dk", + "fi", + "hu", + "no", + "pl", + "pt", + "ru", + "ua", + "ch", + "br", + "nz", + "mx", + "au" + ] + } + }, + { + "name": "taxonomy", + "in": "query", + "description": "Filters by Google Content Categories. This field will accept 1 or more categories, must pass the full name of the category. Example: taxonomy=/Finance/Banking/Other, /Finance/Investing/Funds", + "schema": { + "type": "string" + } + }, + { + "name": "topTaxonomies", + "in": "query", + "description": "Filters by Google Content Categories. Highlights the top 3 categories in a cluster, ordered by count.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "200", + "content": { + "application/json": { + "examples": { + "Result": { + "value": "{\n \"status\": 200,\n \"numResults\": 18,\n \"results\": [\n {\n \"createdAt\": \"2025-01-29T08:11:03.602007+00:00\",\n \"updatedAt\": \"2025-01-29T14:46:26.744051+00:00\",\n \"initializedAt\": \"2025-01-29T08:42:16.214884+00:00\",\n \"id\": \"dc0612a3ff094e94be93f5713db2a940\",\n \"name\": \"Norway's Wealth Fund Reports Record $222 Billion Profit\",\n \"summary\": \"Norway's $1.8 trillion sovereign wealth fund announced a record annual profit of 2.51 trillion crowns ($222 billion) for 2024, primarily driven by a strong performance in the technology sector. CEO Nicolai Tangen noted that American tech giants like Apple, Microsoft, and Nvidia played a crucial role in this success, contributing to an overall return on investment of 13%. Despite this impressive profit, state inflows into the fund were lower than previous records, and returns from fixed income and real estate investments lagged. The fund, which averages 1.5% ownership of publicly listed stocks worldwide, continues to leverage revenues from Norway's oil and gas production. This milestone underscores the fund's strategic importance in bolstering Norway's economic position globally. Overall, the tech rally significantly enhanced the fund's financial standing in 2024.\",\n \"summaryReferences\": [\n \"5e5c0b136c214f588e4bcc18d9bcc1bc\",\n \"ffe6384f9f214f3782d99f06d56af619\",\n \"ac1002cc44f44632b4a29e2152e895cb\",\n \"f3dac6be5b5d42aa8a0c49f6c4fff237\",\n \"68c97189db94453597766a8b6bb6faf0\"\n ],\n \"keyPoints\": [\n {\n \"point\": \"Nicolai Tangen noted, 'Despite the impressive profit, the return on investment was slightly below our benchmark index,' indicating a need for improvement in future results.\",\n \"references\": [\n \"5e5c0b136c214f588e4bcc18d9bcc1bc\"\n ]\n },\n {\n \"point\": \"The fund's equity investments yielded an 18% return, while fixed income investments only saw a 1% increase, showcasing a stark contrast in performance across asset classes.\",\n \"references\": [\n \"ffe6384f9f214f3782d99f06d56af619\"\n ]\n },\n {\n \"point\": \"State inflows into the fund amounted to 402 billion crowns, highlighting a decrease compared to the previous record high of 2022, which affects future investment capabilities.\",\n \"references\": [\n \"ac1002cc44f44632b4a29e2152e895cb\"\n ]\n }\n ],\n \"sentiment\": {\n \"positive\": 0.7317738,\n \"negative\": 0.11641231,\n \"neutral\": 0.15181391\n },\n \"uniqueCount\": 15,\n \"reprintCount\": 14,\n \"totalCount\": 29,\n \"countries\": [\n {\n \"name\": \"in\",\n \"count\": 6\n },\n {\n \"name\": \"us\",\n \"count\": 14\n },\n {\n \"name\": \"ca\",\n \"count\": 3\n },\n {\n \"name\": \"ie\",\n \"count\": 1\n },\n {\n \"name\": \"am\",\n \"count\": 1\n },\n {\n \"name\": \"ae\",\n \"count\": 1\n },\n {\n \"name\": \"my\",\n \"count\": 1\n },\n {\n \"name\": \"ph\",\n \"count\": 1\n }\n ],\n \"topCountries\": [\n \"us\"\n ],\n \"topics\": [\n {\n \"name\": \"Odd News\",\n \"count\": 1\n },\n {\n \"name\": \"Markets\",\n \"count\": 5\n },\n {\n \"name\": \"Economy\",\n \"count\": 1\n }\n ],\n \"topTopics\": [\n {\n \"name\": \"Markets\"\n }\n ],\n \"categories\": [\n {\n \"name\": \"Business\",\n \"count\": 7\n },\n {\n \"name\": \"Finance\",\n \"count\": 7\n },\n {\n \"name\": \"World\",\n \"count\": 1\n }\n ],\n \"topCategories\": [\n {\n \"name\": \"Business\"\n },\n {\n \"name\": \"Finance\"\n }\n ],\n \"taxonomies\": [\n {\n \"name\": \"/News/Business News/Financial Markets News\",\n \"count\": 26\n },\n {\n \"name\": \"/Finance/Investing/Other\",\n \"count\": 26\n },\n {\n \"name\": \"/News/Business News/Other\",\n \"count\": 23\n },\n {\n \"name\": \"/News/Business News/Company News\",\n \"count\": 26\n },\n {\n \"name\": \"/Finance/Investing/Funds\",\n \"count\": 21\n },\n {\n \"name\": \"/News/Business News/Economy News\",\n \"count\": 7\n },\n {\n \"name\": \"/Finance/Investing/Stocks & Bonds\",\n \"count\": 25\n },\n {\n \"name\": \"/Finance/Financial Planning & Management/Asset & Portfolio Management\",\n \"count\": 1\n },\n {\n \"name\": \"/News/Technology News\",\n \"count\": 2\n }\n ],\n \"topTaxonomies\": [\n {\n \"name\": \"/News/Business News/Financial Markets News\"\n },\n {\n \"name\": \"/Finance/Investing/Other\"\n },\n {\n \"name\": \"/News/Business News/Company News\"\n },\n {\n \"name\": \"/Finance/Investing/Stocks & Bonds\"\n },\n {\n \"name\": \"/News/Business News/Other\"\n },\n {\n \"name\": \"/Finance/Investing/Funds\"\n }\n ],\n \"people\": [\n {\n \"wikidataId\": \"Q56408252\",\n \"name\": \"Nicolai Tangen\",\n \"count\": 24\n },\n {\n \"wikidataId\": \"Q22686\",\n \"name\": \"Donald Trump\",\n \"count\": 2\n }\n ],\n \"topPeople\": [\n {\n \"wikidataId\": \"Q56408252\",\n \"name\": \"Nicolai Tangen\"\n }\n ],\n \"companies\": [\n {\n \"id\": \"4de51cf8472b4ce7a5aecdf52a1de4c0\",\n \"name\": \"Microsoft Corporation\",\n \"domains\": [\n \"microsoft.com\"\n ],\n \"symbols\": [\n \"4338.HK\",\n \"MSF.BR\",\n \"MSF.DE\",\n \"MSFT\"\n ],\n \"count\": 2\n },\n {\n \"id\": \"806457af1fd9418db78388760f52c06f\",\n \"name\": \"Alphabet Inc.\",\n \"domains\": [\n \"abc.xyz\"\n ],\n \"symbols\": [\n \"ABEA.DE\",\n \"ABEC.DE\",\n \"GOOG\",\n \"GOOGL\",\n \"GOOGL.SW\"\n ],\n \"count\": 1\n },\n {\n \"id\": \"94bbd0512cd04cc4b7041d78d9d4cf73\",\n \"name\": \"NVIDIA Corporation\",\n \"domains\": [\n \"nvidia.com\"\n ],\n \"symbols\": [\n \"NVD.DE\",\n \"NVDA\"\n ],\n \"count\": 3\n }\n ],\n \"topCompanies\": [],\n \"locations\": [\n {\n \"count\": 5\n },\n {\n \"state\": \"03\",\n \"city\": \"Oslo\",\n \"count\": 9\n }\n ],\n \"topLocations\": [\n {\n \"state\": \"03\",\n \"city\": \"Oslo\"\n },\n {}\n ]\n }," + } + } + } + } + }, + "400": { + "description": "400", + "content": { + "application/json": { + "examples": { + "Result": { + "value": "{\n \"status\": 400,\n \"message\": \"Errors during parameter validation: \\nError on parameter 'updatedFrom', invalid value 'test': Failed to convert property value of type 'java.lang.String' to required type 'java.time.OffsetDateTime' for property 'updatedFrom'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@com.gawq.api.formatting.DateTimeRequestParam java.time.OffsetDateTime] for value [test]; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [test]\",\n \"timestamp\": 1730827027255\n}" + } + }, + "schema": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "example": 400, + "default": 0 + }, + "message": { + "type": "string", + "example": "Errors during parameter validation: \nError on parameter 'updatedFrom', invalid value 'test': Failed to convert property value of type 'java.lang.String' to required type 'java.time.OffsetDateTime' for property 'updatedFrom'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@com.gawq.api.formatting.DateTimeRequestParam java.time.OffsetDateTime] for value [test]; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [test]" + }, + "timestamp": { + "type": "integer", + "example": 1730827027255, + "default": 0 + } + } + } + } + } + } + }, + "deprecated": false, + "security": [], + "x-readme": { + "code-samples": [ + { + "language": "curl", + "code": "$ curl 'https://api.goperigon.com/v1/stories/all?category=Business&from=2023-03-01&apiKey=API_KEY", + "name": "curl" + }, + { + "language": "python", + "code": "import requests\n\nAPI_KEY = \"API_KEY\"\nurl = f\"https://api.goperigon.com/v1/all?category=Business&apiKey={API_KEY}\"\n\nresp = requests.get(url)\n\nprint(resp.json())", + "name": "python" + } + ], + "samples-languages": [ + "curl", + "python" + ] + } + } + } + }, + "x-readme": { + "headers": [ + { + "key": "Accept", + "value": "application/json" + } + ] + }, + "x-readme-fauxas": true +} \ No newline at end of file diff --git a/packages/perigon/package.json b/packages/perigon/package.json new file mode 100644 index 0000000..c4a7686 --- /dev/null +++ b/packages/perigon/package.json @@ -0,0 +1,12 @@ +{ + "name": "@web-agent-rs/perigon", + "version": "0.0.0", + "main": "index.ts", + "types": "./index.d.ts", + "type": "module", + "dependencies": { + "api": "^6.1.3", + "json-schema-to-ts": "^2.8.0-beta.0", + "oas": "^20.11.0" + } +} diff --git a/packages/perigon/schemas.ts b/packages/perigon/schemas.ts new file mode 100644 index 0000000..7a0dc4c --- /dev/null +++ b/packages/perigon/schemas.ts @@ -0,0 +1,5 @@ +const AllNews = {"metadata":{"allOf":[{"type":"object","properties":{"apiKey":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#"},"q":{"type":"string","default":"recall OR \"safety concern*\"","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search query, each article will be scored and ranked against it. Articles are searched on the title, description, and content fields. More ➜"},"title":{"type":"string","default":"tesla OR TSLA OR \"General Motors\" OR GM","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search article headlines/title field. Semantic similar to q parameter."},"desc":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search query on the description field. Semantic similar to q parameter."},"content":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search query on the article's body of content field. Semantic similar to q parameter."},"url":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search query on the url field. Semantic similar to q parameter. E.g. could be used for querying certain website sections, e.g. source=cnn.com&url=travel."},"articleId":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Article ID will search for a news article by the ID of the article. If several parameters are passed, all matched articles will be returned."},"clusterId":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search for related content using a cluster ID. Passing a cluster ID will filter results to only the content found within the cluster."},"from":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'from' filter, will search articles published after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00"},"to":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'to' filter, will search articles published before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T23:59:59"},"addDateFrom":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'addDateFrom' filter, will search articles added after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T00:00:00"},"addDateTo":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'addDateTo' filter, will search articles added before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T23:59:59"},"refreshDateFrom":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Will search articles that were refreshed after the specified date. The date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T00:00:00"},"refreshDateTo":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Will search articles that were refreshed before the specified date. The date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2022-02-01T23:59:59"},"medium":{"type":"string","enum":["Article","Video"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Medium will filter out news articles medium, which could be 'Video' or 'Article'. If several parameters are passed, all matched articles will be returned."},"source":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Publisher's domain can include a subdomain. If multiple parameters are passed, they will be applied as OR operations. Wildcards (\\* and ?) are suported (e.g. \\*.cnn.com). More ➜"},"sourceGroup":{"type":"string","enum":["top10","top100","top25crypto","top25finance","top50tech","top100sports","top100leftUS","top100rightUS","top100centerUS"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"One of the supported source groups. Find Source Groups in the guided part of our documentation... More ➜"},"excludeSource":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"The domain of the website, which should be excluded from the search. Multiple parameters could be provided. Wildcards (\\* and ?) are suported (e.g. \\*.cnn.com)."},"paywall":{"type":"boolean","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter to show only results where the source has a paywall (true) or does not have a paywall (false)."},"byline":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Author names to filter by. Article author bylines are used as a source field. If multiple parameters are passed, they will be applied as OR operations."},"journalistId":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by journalist ID. Journalist IDs are unique journalist identifiers which can be found through the Journalist API, or in the matchedAuthors field. More ➜"},"language":{"type":"string","enum":["da","de","en","es","fi","fr","hu","it","nl","no","pl","pt","ru","sv","uk"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Language code to filter by language. If multiple parameters are passed, they will be applied as OR operations. More ➜"},"searchTranslation":{"type":"boolean","default":false,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Expand a query to search the translation, translatedTitle, and translatedDescription fields for non-English articles."},"label":{"type":"string","enum":["Opinion","Non-news","Paid News","Fact Check","Pop Culture","Roundup","Press Release","Low Content"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Labels to filter by, could be 'Opinion', 'Paid-news', 'Non-news', etc. If multiple parameters are passed, they will be applied as OR operations. More ➜"},"excludeLabel":{"type":"string","enum":["Opinion","Non-news","Paid News","Fact Check","Pop Culture","Roundup","Press Release","Low Content"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Exclude results that include specific labels (Opinion, Non-news, Paid News, etc.). You can filter multiple by repeating the parameter."},"category":{"type":"string","enum":["Politics","Tech","Sports","Business","Finance","Entertainment","Health","Weather","Lifestyle","Auto","Science","Travel","Environment","World","General","none"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by categories. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. Use 'none' to search uncategorized articles. More ➜"},"topic":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by topics. Each topic is some kind of entity that the article is about. Examples of topics: Markets, Joe Biden, Green Energy, Climate Change, Cryptocurrency, etc. If multiple parameters are passed, they will be applied as OR operations. More ➜"},"excludeTopic":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by excluding topics. Each topic is some kind of entity that the article is about. Examples of topics: Markets, Joe Biden, Green Energy, Climate Change, Cryptocurrency, etc. If multiple parameters are passed, they will be applied as OR operations. More ➜"},"linkTo":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Returns only articles that point to specified links (as determined by the 'links' field in the article response)."},"showReprints":{"type":"boolean","default":true,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Whether to return reprints in the response or not. Reprints are usually wired articles from sources like AP or Reuters that are reprinted in multiple sources at the same time. By default, this parameter is 'true'."},"reprintGroupId":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Shows all articles belonging to the same reprint group. A reprint group includes one original article (the first one processed by the API) and all its known reprints."},"city":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters articles where a specified city plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the urban area in question. If multiple parameters are passed, they will be applied as OR operations."},"area":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters articles where a specified area, such as a neighborhood, borough, or district, plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the area in question. If multiple parameters are passed, they will be applied as OR operations."},"state":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters articles where a specified state plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the state in question. If multiple parameters are passed, they will be applied as OR operations."},"locationsCountry":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters articles where a specified country plays a central role in the content, beyond mere mentions, to ensure the results are deeply relevant to the country in question. If multiple parameters are passed, they will be applied as OR operations."},"excludeLocationsCountry":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Excludes articles where a specified country plays a central role in the content, ensuring results are not deeply relevant to the country in question. If multiple parameters are passed, they will be applied as AND operations, excluding articles relevant to any of the specified countries."},"location":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Return all articles that have the specified location. Location attributes are delimited by ':' between key and value, and '::' between attributes. Example: 'city:New York::state:NY'."},"lat":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Latitude of the center point to search places"},"lon":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Longitude of the center point to search places"},"maxDistance":{"type":"integer","format":"int32","minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Maximum distance (in km) from starting point to search articles by tagged places"},"sourceCity":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Find articles published by sources that are located within a given city."},"sourceCounty":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Find articles published by sources that are located within a given county."},"sourceCountry":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Find articles published by sources that are located within a given country. Must be 2 character country code (i.e. us, gb, etc)."},"country":{"type":"string","enum":["us","gb","de","it","fr","nl","se","dk","fi","hu","no","pl","pt","ru","ua","ch","br","nz","mx","au"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations."},"sourceState":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Find articles published by sources that are located within a given state."},"sourceLon":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Latitude of the center point to search articles created by local publications."},"sourceLat":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Latitude of the center point to search articles created by local publications."},"sourceMaxDistance":{"type":"integer","format":"int32","minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Maximum distance from starting point to search articles created by local publications."},"personWikidataId":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of person Wikidata IDs for filtering."},"personName":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of person names for exact matches. Boolean and complex logic is not supported on this paramter."},"companyId":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of company IDs to filter by."},"companyName":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search by company name."},"companyDomain":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search by company domains for filtering. E.g. companyDomain=apple.com."},"companySymbol":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search by company symbols."},"page":{"type":"integer","format":"int32","minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Zero-based page number. From 0 to 10000. See the Pagination section for limitations."},"size":{"type":"integer","format":"int32","minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Number of articles returned per page, from 0 to 100."},"sortBy":{"type":"string","enum":["date","relevance","addDate","pubDate","refreshDate"],"default":"relevance","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'relevance' to sort by relevance to the query, 'date' to sort by the publication date (desc), 'pubDate' is a synonym to 'date', 'addDate' to sort by 'addDate' field (desc), 'refreshDate' to sort by 'refreshDate' field (desc)."},"showNumResults":{"type":"boolean","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Whether to show the total number of all matched articles. Default value is false which makes queries a bit more efficient but also counts up to 10000 articles."},"positiveSentimentFrom":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score greater than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores."},"positiveSentimentTo":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score less than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores."},"neutralSentimentFrom":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score greater than or equal to the specified value, indicating neutral sentiment. Explanation of sentimental values can be found in Docs under the Article Data section."},"neutralSentimentTo":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score less than or equal to the specified value, indicating neutral sentiment. See the Article Data section in Docs for an explanation of scores."},"negativeSentimentFrom":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score greater than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores."},"negativeSentimentTo":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score less than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores."},"taxonomy":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters by Google Content Categories. This field will accept 1 or more categories, must pass the full name of the category. Example: taxonomy=/Finance/Banking/Other, /Finance/Investing/Funds"},"prefixTaxonomy":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters by Google Content Categories. This field will filter by the category prefix only. Example: prefixTaxonomy=/Finance"}},"required":["apiKey"]}]},"response":{"200":{"type":"object","properties":{"status":{"type":"integer","default":0,"examples":[200]},"numResults":{"type":"integer","default":0,"examples":[1]},"articles":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string","examples":["https://decrypt.co/301053/nft-market-hits-three-year-low-in-trading-and-sales-report"]},"authorsByline":{"type":"string","examples":["Vismaya V"]},"articleId":{"type":"string","examples":["680d7893185c4357a9047332cf38206c"]},"clusterId":{"type":"string","examples":["37426b0543cb45e3a547695fd63e9919"]},"source":{"type":"object","properties":{"domain":{"type":"string","examples":["decrypt.co"]},"paywall":{"type":"boolean","default":true,"examples":[false]},"location":{"type":"object","properties":{"country":{"type":"string","examples":["us"]},"state":{"type":"string","examples":["NY"]},"city":{"type":"string","examples":["New York"]},"coordinates":{"type":"object","properties":{"lat":{"type":"number","default":0,"examples":[40.7127281]},"lon":{"type":"number","default":0,"examples":[-74.0060152]}}}}}}},"imageUrl":{"type":"string","examples":["https://cdn.decrypt.co/resize/1024/height/512/wp-content/uploads/2021/07/The-Bored-Ape-Yacht-Club-gID_7.jpeg"]},"country":{"type":"string","examples":["us"]},"language":{"type":"string","examples":["en"]},"pubDate":{"type":"string","examples":["2025-01-15T09:20:34+00:00"]},"addDate":{"type":"string","examples":["2025-01-15T09:27:57.156032+00:00"]},"refreshDate":{"type":"string","examples":["2025-01-15T09:27:57.156034+00:00"]},"score":{"type":"number","default":0,"examples":[52.79721]},"title":{"type":"string","examples":["NFT Market Hits Three-Year Low in Trading and Sales: Report"]},"description":{"type":"string","examples":["Annual NFT trading volumes fell by 19%, while sales counts dipped by 18% compared to 2023, according to DappRadar."]},"content":{"type":"string","examples":["The NFT market suffered a dismal 2024, with trading volumes and sales counts dropping to their weakest levels since 2020.\n\nAnnual trading volumes fell by 19%, while sales counts dipped by 18% compared to 2023, according to a report by blockchain analytics platform.\n\nDespite a surge in crypto market activity, driven by Bitcoin’s all-time highs and booming DeFi growth, NFTs appeared to struggle under the weight of their own inflated valuations.\n\nEarly in the year, NFT trading volumes reached $5.3 billion in Q1, a modest 4% increase compared to the same period in 2023.\n\nHowever, this momentum proved fleeting, as volumes plummeted to $1.5 billion in Q3 before recovering slightly to $2.6 billion in Q4.\n\nEven with these fluctuations, annual sales counts fell sharply, pointing to a broader trend: while individual NFTs became more expensive in line with rising crypto token prices, overall market engagement dwindled.\n\nYuga Labs’ flagship collections Bored Ape Yacht Club (BAYC) and Mutant Ape Yacht Club (MAYC) hit historic lows, with floor prices dropping to 15 ETH and 2.4 ETH, respectively.\n\nEven Otherdeeds for Yuga Labs' Otherside metaverse plummeted to 0.23 ETH, a far cry from their initial minting price, exposing cracks in Yuga’s high-priced, membership-driven model.\n\nThis coincided with DappRadar’s observation that “Perhaps 2024 helped us realize that NFTs don’t need to be expensive to prove their importance in the broader Web3 ecosystem,” a critique of the market’s reliance on exclusivity and inflated pricing.\n\nAmid this downturn, the NFT market witnessed a paradox in November when CryptoPunk #8348, a rare seven-trait collectible from the NFT collection, was collateralized for a $2.75 million loan via the NFT lending platform GONDI.\n\nTouted as a milestone for NFTs as financial assets, this event showed speculative excess when juxtaposed with DappRadar’s insights about affordability and utility.\n\nWhile high-profile transactions like this aim to affirm NFTs’ value, they also highlight a market still driven by exclusivity and inflated pricing, even as wider participation wanes.\n\nEven within the struggling sector, blue-chip collections like CryptoPunks defied trends, nearly doubling in USD value in 2024 with notable sales driving brief recovery periods.\n\nNFT platforms like Blur dominated marketplace activity, leveraging zero-fee trading and aggressive airdrop campaigns to capture the largest share of trading volumes.\n\nIn contrast, rival marketplace OpenSea struggled with regulatory headwinds and declining market sentiment, forcing significant layoffs by year-end.\n\nBy Q4, Blur and OpenSea were neck-and-neck in market share, but Blur’s ability to generate high activity from a smaller, more active user base gave it the edge, as per the report.\n\nWhile trading volumes in late 2024 hinted at a potential recovery—November sales hit $562 million, the highest since May—the overall trajectory suggests that affordability, accessibility, and utility will be critical for sustained growth in 2025."]},"medium":{"type":"string","examples":["Article"]},"links":{"type":"array","items":{"type":"string","examples":["https://dappradar.com/blog/dapp-industry-report-2024-overview"]}},"labels":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","examples":["Roundup"]}}}},"matchedAuthors":{"type":"array","items":{"type":"object","properties":{"id":{},"name":{"type":"string","examples":["Vismaya V"]}}}},"claim":{"type":"string","examples":[""]},"verdict":{"type":"string","examples":[""]},"keywords":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","examples":["NFT trading volumes"]},"weight":{"type":"number","default":0,"examples":[0.095650345]}}}},"topics":{"type":"array","items":{}},"categories":{"type":"array","items":{}},"taxonomies":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","examples":["/Finance/Investing/Currencies & Foreign Exchange"]}}}},"entities":{"type":"array","items":{"type":"object","properties":{"data":{"type":"string","examples":["Yuga Labs’"]},"type":{"type":"string","examples":["ORG"]},"mentions":{"type":"integer","default":0,"examples":[3]}}}},"companies":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","examples":["f0886b1323a142df88f521ba9a0bbedf"]},"name":{"type":"string","examples":["OpenSea Ventures"]},"domains":{"type":"array","items":{"type":"string","examples":["opensea.io"]}},"symbols":{"type":"array","items":{}}}}},"sentiment":{"type":"object","properties":{"positive":{"type":"number","default":0,"examples":[0.033333376]},"negative":{"type":"number","default":0,"examples":[0.89130986]},"neutral":{"type":"number","default":0,"examples":[0.07535672]}}},"summary":{"type":"string","examples":["The National Finance Finance (NFT) market experienced a three-year low in 2024, with annual trading volumes and sales counts dropping to their lowest levels since 2020. The report by blockchain analytics platform DappRadar suggests that despite a surge in crypto market activity driven by Bitcoin's all-time highs and DeFi growth, NFTs struggled under the weight of their inflated valuations. Despite early Q1 trading volumes reaching $5.3 billion, a 4% increase compared to the same period in 2023, volumes dropped to $1.5 billion in Q3 before recovering to $2.6 billion by Q4. Despite these fluctuations, overall market engagement decreased. Despite this downturn, high-profile transactions like CryptoPunk #8348, a rare seven-trait collectible from the NFT collection, were seen as a milestone for NFT's value and highlighted a market still driven by exclusivity and inflated pricing."]},"translation":{"type":"string","examples":[""]},"translatedTitle":{"type":"string","examples":[""]},"translatedDescription":{"type":"string","examples":[""]},"translatedSummary":{"type":"string","examples":[""]},"locations":{"type":"array","items":{}},"reprint":{"type":"boolean","default":true,"examples":[false]},"reprintGroupId":{"type":"string","examples":["3d67e5bf389b40b3895ae0708f4dd208"]},"places":{"type":"array","items":{}},"people":{"type":"array","items":{}}}}}},"$schema":"https://json-schema.org/draft/2020-12/schema#"},"400":{"$schema":"https://json-schema.org/draft/2020-12/schema#"},"401":{"$schema":"https://json-schema.org/draft/2020-12/schema#"},"403":{"$schema":"https://json-schema.org/draft/2020-12/schema#"},"404":{"$schema":"https://json-schema.org/draft/2020-12/schema#"},"500":{"$schema":"https://json-schema.org/draft/2020-12/schema#"}}} as const +; +const Stories1 = {"metadata":{"allOf":[{"type":"object","properties":{"apiKey":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#"},"q":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search story by name, summary and key points. Preference is given to the name field. Supports complex query syntax, same way as **q** parameter from **/all** endpoint."},"name":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Search story by name. Supports complex query syntax, same way as **q** parameter from **/all** endpoint."},"topic":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by topics. Each topic is some kind of entity that the article is about. Examples of topics: Markets, Joe Biden, Green Energy, Climate Change, Cryptocurrency, etc. If multiple parameters are passed, they will be applied as OR operations. More ➜"},"category":{"type":"string","enum":["Politics","Tech","Sports","Business","Finance","Entertainment","Health","Weather","Lifestyle","Auto","Science","Travel","Environment","World","General","none"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by categories. Categories are general themes that the article is about. Examples of categories: Tech, Politics, etc. If multiple parameters are passed, they will be applied as OR operations. Use 'none' to search uncategorized articles. More ➜"},"clusterId":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter to specific story. Passing a cluster ID will filter results to only the content found within the cluster. Multiple params could be passed."},"source":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter stories by sources that wrote articles belonging to this story. At least 1 article is required for story to match. Multiple parameters could be passed."},"sourceGroup":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter stories by sources that wrote articles belonging to this story. Source groups are expanded into a list of sources. At least 1 article by the source is required for story to match. Multiple params could be passed."},"personWikidataId":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of person Wikidata IDs for filtering. Filter is applied on topPeople field."},"personName":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of people names. Filtering is applied on topPeople field."},"companyId":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of company IDs for filtering. Filtering is applied to topCompanies field."},"companyName":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of company names for filtering. Filtering is applied on topCompanies field."},"companyDomain":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of company domains for filtering. Filtering is applied on topCompanies field."},"companySymbol":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"List of company tickers for filtering. Filtering is applied on topCompanies field."},"nameExists":{"type":"boolean","default":true,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Returns stories with name assigned. Defaults to true."},"from":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'from' filter, will search stories created after the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T00:00:00"},"to":{"type":"string","format":"date-time","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'to' filter, will search stories created before the specified date, the date could be passed as ISO or 'yyyy-mm-dd'. Add time in ISO format, ie. 2023-03-01T23:59:59"},"initializedFrom":{"type":"string","format":"date","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'initializedFrom' filter, will search stories that became available after provided date"},"initializedTo":{"type":"string","format":"date","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"'initializedTo' filter, will search stories that became available before provided date"},"updatedFrom":{"type":"string","format":"date","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Will return stories with 'updatedAt' >= 'updatedFrom'."},"updatedTo":{"type":"string","format":"date","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Will return stories with 'updatedAt' <= 'updatedTo'."},"minClusterSize":{"type":"integer","format":"int32","default":5,"minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by minimum cluster size. Minimum cluster size filter applies to number of **unique** articles."},"maxClusterSize":{"type":"integer","format":"int32","minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter by maximum cluster size. Maximum cluster size filter applies to number of **unique** articles in the cluster."},"state":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter local news by state. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations."},"city":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter local news by city. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations. More ➜"},"area":{"type":"array","items":{"type":"string"},"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filter local news by area. Applies only to local news, when this param is passed non-local news will not be returned. If multiple parameters are passed, they will be applied as OR operations."},"page":{"type":"integer","format":"int32","default":0,"minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Zero-based page number. From 0 to 10000. See the Pagination section for limitations."},"size":{"type":"integer","format":"int32","default":10,"minimum":-2147483648,"maximum":2147483647,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Number of stories results per page, from 0 to 100."},"sortBy":{"type":"string","enum":["createdAt","updatedAt","count"],"default":"createdAt","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Sort stories by count, creation date, last updated date. By default is sorted by created at."},"showNumResults":{"type":"boolean","default":false,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Show total number of results. By default set to false, will cap result count at 10000."},"showDuplicates":{"type":"boolean","default":false,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Stories are deduplicated by default. If a story is deduplicated, all future articles are merged into the original story. *duplicateOf* field contains the original cluster Id. When *showDuplicates=true*, all stories are shown."},"positiveSentimentFrom":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score greater than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores."},"positiveSentimentTo":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score less than or equal to the specified value, indicating positive sentiment. See the Article Data section in Docs for an explanation of scores."},"neutralSentimentFrom":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score greater than or equal to the specified value, indicating neutral sentiment. Explanation of sentimental values can be found in Docs under the Article Data section."},"neutralSentimentTo":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score less than or equal to the specified value, indicating neutral sentiment. See the Article Data section in Docs for an explanation of scores."},"negativeSentimentFrom":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score greater than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores."},"negativeSentimentTo":{"type":"number","format":"float","minimum":-3.402823669209385e+38,"maximum":3.402823669209385e+38,"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters results with a sentiment score less than or equal to the specified value, indicating negative sentiment. See the Article Data section in Docs for an explanation of scores."},"country":{"type":"string","enum":["us","gb","de","it","fr","nl","se","dk","fi","hu","no","pl","pt","ru","ua","ch","br","nz","mx","au"],"$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Country code to filter by country. If multiple parameters are passed, they will be applied as OR operations."},"taxonomy":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters by Google Content Categories. This field will accept 1 or more categories, must pass the full name of the category. Example: taxonomy=/Finance/Banking/Other, /Finance/Investing/Funds"},"topTaxonomies":{"type":"string","$schema":"https://json-schema.org/draft/2020-12/schema#","description":"Filters by Google Content Categories. Highlights the top 3 categories in a cluster, ordered by count."}},"required":["apiKey"]}]},"response":{"200":{"$schema":"https://json-schema.org/draft/2020-12/schema#"},"400":{"type":"object","properties":{"status":{"type":"integer","default":0,"examples":[400]},"message":{"type":"string","examples":["Errors during parameter validation: \nError on parameter 'updatedFrom', invalid value 'test': Failed to convert property value of type 'java.lang.String' to required type 'java.time.OffsetDateTime' for property 'updatedFrom'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@com.gawq.api.formatting.DateTimeRequestParam java.time.OffsetDateTime] for value [test]; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [test]"]},"timestamp":{"type":"integer","default":0,"examples":[1730827027255]}},"$schema":"https://json-schema.org/draft/2020-12/schema#"}}} as const +; +export { AllNews, Stories1 } diff --git a/packages/perigon/types.ts b/packages/perigon/types.ts new file mode 100644 index 0000000..83c1620 --- /dev/null +++ b/packages/perigon/types.ts @@ -0,0 +1,13 @@ +import type { FromSchema } from 'json-schema-to-ts'; +import * as schemas from './schemas'; + +export type AllNewsMetadataParam = FromSchema; +export type AllNewsResponse200 = FromSchema; +export type AllNewsResponse400 = FromSchema; +export type AllNewsResponse401 = FromSchema; +export type AllNewsResponse403 = FromSchema; +export type AllNewsResponse404 = FromSchema; +export type AllNewsResponse500 = FromSchema; +export type Stories1MetadataParam = FromSchema; +export type Stories1Response200 = FromSchema; +export type Stories1Response400 = FromSchema; diff --git a/searxng_tester.mts b/searxng_tester.mts new file mode 100644 index 0000000..a5553a3 --- /dev/null +++ b/searxng_tester.mts @@ -0,0 +1,29 @@ +import { SearxngService, type SearxngServiceConfig, type SearxngSearchResult } from 'searxng'; + +const config: SearxngServiceConfig = { + baseURL: 'https://search-engine-gsio.fly.dev', + defaultSearchParams: { + format: 'json', + lang: 'auto', + }, + defaultRequestHeaders: { + 'Content-Type': 'application/json', + }, +}; + +const searxngService = new SearxngService(config); + +async function performSearch(query) { + try { + const results = await searxngService.search(query); + console.log(results); + return results; + } catch (error) { + console.error('Search failed:', error); + } +} + + +const results = await performSearch('dogs'); + +console.log(JSON.stringify(results)); \ No newline at end of file diff --git a/src/agents/crypto_market.rs b/src/agents/crypto_market.rs new file mode 100644 index 0000000..989dfb6 --- /dev/null +++ b/src/agents/crypto_market.rs @@ -0,0 +1,28 @@ +use tokio::process::Child; +use tracing; + +use crate::utils::utils::run_agent; + +pub async fn finance_query_agent(stream_id: &str, input: &str) -> Result { + run_agent(stream_id, input, "./packages/genaiscript/genaisrc/finance-query.genai.mts").await +} + + +// #[cfg(test)] +// mod tests { +// use std::fmt::Debug; +// use crate::agents::search::search_agent; +// +// #[tokio::test] // Mark the test function as async +// async fn test_search_execution() { +// let input = "Who won the 2024 presidential election?"; +// +// let mut command = search_agent("test-stream", input).await.unwrap(); +// +// // command.stdout.take().unwrap().read_to_string(&mut String::new()).await.unwrap(); +// // Optionally, you can capture and inspect stdout if needed: +// let output = command.wait_with_output().await.expect("Failed to wait for output"); +// println!("Stdout: {}", String::from_utf8_lossy(&output.stdout)); +// println!("Stderr: {}", String::from_utf8_lossy(&output.stderr)); +// } +// } diff --git a/src/agents/image_generator.rs b/src/agents/image_generator.rs new file mode 100644 index 0000000..a3502fc --- /dev/null +++ b/src/agents/image_generator.rs @@ -0,0 +1,10 @@ +use crate::utils::utils::run_agent; +use tokio::process::Child; + +pub async fn image_generator(stream_id: &str, input: &str) -> Result { + tracing::debug!( + "Running image generator, \ninput: {}", + input + ); + run_agent(stream_id, input, "./packages/genaiscript/genaisrc/image-generator.genai.mts").await +} diff --git a/src/agents/mod.rs b/src/agents/mod.rs new file mode 100644 index 0000000..a7ff97d --- /dev/null +++ b/src/agents/mod.rs @@ -0,0 +1,5 @@ +pub mod news; +pub mod scrape; +pub mod search; +pub mod image_generator; +pub mod crypto_market; diff --git a/src/agents/news.rs b/src/agents/news.rs new file mode 100644 index 0000000..74cb0cb --- /dev/null +++ b/src/agents/news.rs @@ -0,0 +1,6 @@ +use crate::utils::utils::run_agent; +use tokio::process::Child; + +pub async fn news_agent(stream_id: &str, input: &str) -> Result { + run_agent(stream_id, input, "./packages/genaiscript/genaisrc/news-search.genai.mts").await +} diff --git a/src/agents/scrape.rs b/src/agents/scrape.rs new file mode 100644 index 0000000..ecd16ac --- /dev/null +++ b/src/agents/scrape.rs @@ -0,0 +1,6 @@ +use crate::utils::utils::run_agent; +use tokio::process::Child; + +pub async fn scrape_agent(stream_id: &str, input: &str) -> Result { + run_agent(stream_id, input, "./packages/genaiscript/genaisrc/web-scrape.genai.mts").await +} diff --git a/src/agents/search.rs b/src/agents/search.rs new file mode 100644 index 0000000..de385c8 --- /dev/null +++ b/src/agents/search.rs @@ -0,0 +1,28 @@ +use tokio::process::Child; +use tracing; + +use crate::utils::utils::run_agent; + +pub async fn search_agent(stream_id: &str, input: &str) -> Result { + run_agent(stream_id, input, "./packages/genaiscript/genaisrc/web-search.genai.mts").await +} + + +#[cfg(test)] +mod tests { + use std::fmt::Debug; + use crate::agents::search::search_agent; + + #[tokio::test] + async fn test_search_execution() { + let input = "Who won the 2024 presidential election?"; + + let mut command = search_agent("test-stream", input).await.unwrap(); + + // command.stdout.take().unwrap().read_to_string(&mut String::new()).await.unwrap(); + // Optionally, you can capture and inspect stdout if needed: + let output = command.wait_with_output().await.expect("Failed to wait for output"); + println!("Stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("Stderr: {}", String::from_utf8_lossy(&output.stderr)); + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..6349d06 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,30 @@ +// src/config.rs +pub struct AppConfig { + pub env_vars: Vec, +} + + +impl AppConfig { + pub fn new() -> Self { + // Load .env file if it exists + match dotenv::dotenv() { + Ok(_) => tracing::debug!("Loaded .env file successfully"), + Err(e) => tracing::debug!("No .env file found or error loading it: {}", e), + } + + Self { + env_vars: vec![ + "OPENAI_API_KEY".to_string(), + "BING_SEARCH_API_KEY".to_string(), + "TAVILY_API_KEY".to_string(), + "GENAISCRIPT_MODEL_LARGE".to_string(), + "GENAISCRIPT_MODEL_SMALL".to_string(), + "SEARXNG_API_BASE_URL".to_string(), + ], + } + } + + pub fn get_env_var(&self, key: &str) -> String { + std::env::var(key).unwrap_or_default() + } +} \ No newline at end of file diff --git a/src/genaiscript.rs b/src/genaiscript.rs new file mode 100644 index 0000000..dd9cdfa --- /dev/null +++ b/src/genaiscript.rs @@ -0,0 +1,90 @@ +use std::collections::HashMap; +use std::path::PathBuf; +use tokio::process::{Child, Command}; +use tracing; + +const DEFAULT_ENV_VARS: [&str; 4] = [ + "OPENAI_API_KEY", + "OPENAI_API_BASE", + "GENAISCRIPT_MODEL_LARGE", + "GENAISCRIPT_MODEL_SMALL", +]; + +pub struct GenAIScriptConfig { + script_path: PathBuf, + output_dir: PathBuf, + stream_id: String, + user_input: String, + retry_count: u32, + env_vars: HashMap, +} + +impl GenAIScriptConfig { + pub fn new(script_path: impl Into, stream_id: impl Into, user_input: impl Into) -> Self { + let mut env_vars = HashMap::new(); + + // Initialize with default environment variables + for var in DEFAULT_ENV_VARS { + if let Ok(value) = std::env::var(var) { + env_vars.insert(var.to_string(), value); + } + } + + Self { + script_path: script_path.into(), + output_dir: PathBuf::from("./web-agent-rs/output"), + stream_id: stream_id.into(), + user_input: user_input.into(), + retry_count: 0, + env_vars, + } + } + + pub fn with_output_dir(mut self, dir: impl Into) -> Self { + self.output_dir = dir.into(); + self + } + + pub fn with_retry_count(mut self, count: u32) -> Self { + self.retry_count = count; + self + } + + pub fn with_additional_env_vars(mut self, vars: HashMap) -> Self { + self.env_vars.extend(vars); + self + } +} + +pub async fn run_genaiscript(config: GenAIScriptConfig) -> Result { + tracing::debug!("Initiating GenAIScript for stream {}", config.stream_id); + + let output_path = config.output_dir.join(&config.stream_id); + + let mut command = Command::new("bunx"); + command + .arg("genaiscript") + .arg("run") + .arg(&config.script_path) + // .arg("--fail-on-errors") + .arg("—out-trace") + .arg(output_path) + .arg("--retry") + .arg(config.retry_count.to_string()) + .arg("--vars") + .arg(format!("USER_INPUT='{}'", config.user_input)); + + // Add environment variables + for (key, value) in config.env_vars { + command.env(key, value); + } + + command + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::inherit()) + .spawn() + .map_err(|e| { + tracing::error!("Failed to spawn genaiscript process: {}", e); + e.to_string() + }) +} \ No newline at end of file diff --git a/src/handlers/error.rs b/src/handlers/error.rs new file mode 100644 index 0000000..8b08792 --- /dev/null +++ b/src/handlers/error.rs @@ -0,0 +1,17 @@ +// src/handlers/error.rs +use axum::{ + http::StatusCode, + Json, + response::IntoResponse, +}; + +pub async fn handle_not_found() -> impl IntoResponse { + tracing::warn!("404 Not Found error occurred"); + + let error_response = serde_json::json!({ + "error": "Route Not Found", + "status": 404 + }); + + (StatusCode::NOT_FOUND, Json(error_response)) +} \ No newline at end of file diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..506514d --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,7 @@ + +// src/handlers/mod.rs +pub mod error; +pub mod status; +pub mod stream; +pub mod ui; +pub mod webhooks; \ No newline at end of file diff --git a/src/handlers/status.rs b/src/handlers/status.rs new file mode 100644 index 0000000..1bb1b56 --- /dev/null +++ b/src/handlers/status.rs @@ -0,0 +1,5 @@ +// src/handlers/status.rs +pub async fn handle_status() -> &'static str { + tracing::debug!("Status check requested"); + "Server is running" +} \ No newline at end of file diff --git a/src/handlers/stream.rs b/src/handlers/stream.rs new file mode 100644 index 0000000..c01cddd --- /dev/null +++ b/src/handlers/stream.rs @@ -0,0 +1,82 @@ +use axum::{ + body::Body, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use futures::StreamExt; +use tokio_util::io::ReaderStream; + +pub async fn handle_stream() -> impl IntoResponse { + use tokio::process::Command; + + let user_input = "Who won the 2024 election?"; + tracing::debug!("Handling stream request with input: {}", user_input); + + // Check environment variables + for env_var in ["OPENAI_API_KEY", "BING_SEARCH_API_KEY", "TAVILY_API_KEY"] { + if std::env::var(env_var).is_ok() { + tracing::debug!("{} is set", env_var); + } else { + tracing::warn!("{} is not set", env_var); + } + } + + let mut cmd = match Command::new("genaiscript") + .arg("run") + .arg("genaisrc/web-search.genai.mts") + .arg("--vars") + .arg(format!("USER_INPUT='{}'", user_input)) + .env("OPENAI_API_KEY", std::env::var("OPENAI_API_KEY").unwrap_or_default()) + .env("BING_SEARCH_API_KEY", std::env::var("BING_SEARCH_API_KEY").unwrap_or_default()) + .env("TAVILY_API_KEY", std::env::var("TAVILY_API_KEY").unwrap_or_default()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::null()) + .spawn() { + Ok(cmd) => { + tracing::debug!("Successfully spawned genaiscript process"); + cmd + } + Err(e) => { + tracing::error!("Failed to spawn genaiscript process: {}", e); + return Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from("Failed to start process")) + .unwrap(); + } + }; + + let stdout = match cmd.stdout.take() { + Some(stdout) => { + tracing::debug!("Successfully captured stdout from process"); + stdout + } + None => { + tracing::error!("Failed to capture stdout from process"); + return Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from("Failed to capture process output")) + .unwrap(); + } + }; + + let reader = tokio::io::BufReader::new(stdout); + let stream = ReaderStream::new(reader); + let mapped_stream = stream.map(|r| { + match r { + Ok(bytes) => { + tracing::trace!("Received {} bytes from stream", bytes.len()); + Ok(bytes) + } + Err(e) => { + tracing::error!("Error reading from stream: {}", e); + Err(e) + } + } + }); + + tracing::debug!("Setting up SSE response"); + Response::builder() + .header("Content-Type", "text/event-stream") + .body(Body::from_stream(mapped_stream)) + .unwrap() +} \ No newline at end of file diff --git a/src/handlers/ui.rs b/src/handlers/ui.rs new file mode 100644 index 0000000..bcf5967 --- /dev/null +++ b/src/handlers/ui.rs @@ -0,0 +1,34 @@ +use axum::{ + body::Body, + http::{StatusCode, header::CONTENT_TYPE}, + response::{IntoResponse, Response}, +}; +use rust_embed::RustEmbed; +use tracing::{debug, error}; + +#[derive(RustEmbed)] +#[folder = "assets/"] +struct Asset; + +pub async fn serve_ui() -> impl IntoResponse { + debug!("Serving UI request"); + + // Attempt to retrieve the embedded "index.html" + match Asset::get("index.html") { + Some(content) => { + debug!("Successfully retrieved index.html"); + Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "text/html") + .body(Body::from(content.data)) + .unwrap() + } + None => { + error!("index.html not found in embedded assets"); + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from("404 Not Found")) + .unwrap() + } + } +} \ No newline at end of file diff --git a/src/handlers/webhooks.rs b/src/handlers/webhooks.rs new file mode 100644 index 0000000..88fbd91 --- /dev/null +++ b/src/handlers/webhooks.rs @@ -0,0 +1,261 @@ +use crate::agents; +use crate::agents::news::news_agent; +use crate::agents::scrape::scrape_agent; +use crate::agents::search::search_agent; +use axum::response::Response; +use axum::{ + body::Body, extract::Path, extract::Query, http::StatusCode, response::IntoResponse, Json, +}; +use bytes::Bytes; +use futures::stream::{Stream, StreamExt}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sled; +use std::pin::Pin; +use std::sync::Arc; +use std::time::Duration; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::Command; +use tokio::sync::Mutex; +use crate::agents::crypto_market::finance_query_agent; +use crate::agents::image_generator::image_generator; + +// init sled +lazy_static! { + static ref DB: Arc> = Arc::new(Mutex::new( + sled::open("./web-agent-rs/db/stream_store").expect("Failed to open sled database") + )); +} + +pub async fn handle_webhooks(Path(stream_id): Path) -> impl IntoResponse { + let db = DB.lock().await; + match db.get(&stream_id) { + Ok(Some(data)) => { + + let mut info: StreamInfo = match serde_json::from_slice(&data) { + Ok(info) => info, + Err(e) => { + tracing::error!("Failed to deserialize StreamInfo: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + + // Increment the call_count in the database + info.call_count += 1; + let updated_info_bytes = match serde_json::to_vec(&info) { + Ok(data) => data, + Err(e) => { + tracing::error!("Failed to serialize updated StreamInfo: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + match db.insert(&stream_id, updated_info_bytes) { + Ok(_) => { + if let Err(e) = db.flush_async().await { + tracing::error!("Failed to persist updated call_count to the database: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + } + Err(e) => { + tracing::error!("Failed to update call_count in the database: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + let info: StreamInfo = match db.get(&stream_id) { + Ok(Some(updated_data)) => match serde_json::from_slice(&updated_data) { + Ok(info) => info, + Err(e) => { + tracing::error!("Failed to deserialize updated StreamInfo: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }, + Ok(None) => { + tracing::error!("Stream ID not found after update: {}", stream_id); + return StatusCode::NOT_FOUND.into_response(); + } + Err(e) => { + tracing::error!("Failed to fetch updated record from DB: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + if(info.call_count > 1) { + return StatusCode::OK.into_response(); + } + + let resource = info.resource; + let input = serde_json::to_string(&info.payload.input).unwrap_or_default(); + + tracing::debug!( + "Processing webhook - Resource: {}, Stream ID: {}", + resource, + stream_id + ); + + let cmd = match resource.as_str() { + "web-search" => search_agent(stream_id.as_str(), &*input).await, + "news-search" => news_agent(stream_id.as_str(), &*input).await, + "image-generator" => image_generator(stream_id.as_str(), &*input).await, + "finance-query" => finance_query_agent(stream_id.as_str(), &*input).await, + "web-scrape" => scrape_agent(stream_id.as_str(), &*input).await, + _ => { + tracing::error!("Unsupported resource type: {}", resource); + return StatusCode::BAD_REQUEST.into_response(); + } + }; + + let mut cmd = match cmd { + Ok(cmd) => cmd, + Err(e) => { + tracing::error!("Agent execution failed: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + let stdout = match cmd.stdout.take() { + Some(stdout) => stdout, + None => { + tracing::error!("No stdout available for the command."); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + let reader = BufReader::new(stdout); + let sse_stream = reader_to_stream(reader, stream_id.clone()); + + return Response::builder() + .header("Content-Type", "text/event-stream") + .header("Cache-Control", "no-cache, no-transform") + .header("Connection", "keep-alive") + .header("X-Accel-Buffering", "yes") + .body(Body::from_stream(sse_stream)) + .unwrap() + } + Ok(None) => { + tracing::error!("Stream ID not found: {}", stream_id); + StatusCode::NOT_FOUND.into_response() + } + Err(e) => { + tracing::error!("Failed to fetch from DB: {}", e); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + } +} + +fn reader_to_stream( + reader: BufReader, + stream_id: String, +) -> Pin> + Send>> +where + R: tokio::io::AsyncRead + Unpin + Send + 'static, +{ + let stream = futures::stream::unfold(reader, move |mut reader| async move { + let mut line = String::new(); + match reader.read_line(&mut line).await { + Ok(0) => None, + Ok(_) => Some(( + Ok(Bytes::from(format!("data: {}\n\n", line.trim()))), + reader, + )), + Err(e) => Some((Err(e), reader)), + } + }); + + let stream_with_done = stream.chain(futures::stream::once(async { + Ok(Bytes::from("data: [DONE]\n\n")) + })); + + Box::pin(stream_with_done) +} + +#[derive(Deserialize, Serialize, Debug)] +struct Payload { + input: Value, +} + +#[derive(Serialize, Deserialize, Debug)] +struct StreamInfo { + resource: String, + payload: Payload, + parent: String, + call_count: i32, +} + + +#[derive(Deserialize, Serialize, Debug)] +pub struct WebhookPostRequest { + id: String, + resource: String, + payload: Payload, + parent: String, +} + +#[derive(Deserialize, Serialize, Debug)] +struct WebhookPostResponse { + stream_url: String, +} + +pub async fn handle_webhooks_post(Json(payload): Json) -> impl IntoResponse { + let db = DB.lock().await; + + tracing::info!("Received webhook post request with ID: {}", payload.id); + + let stream_id = payload.id.clone(); + let info = StreamInfo { + resource: payload.resource.clone(), + payload: payload.payload, + parent: payload.parent.clone(), + call_count: 0 + }; + + let info_bytes = match serde_json::to_vec(&info) { + Ok(data) => data, + Err(e) => { + tracing::error!("Failed to serialize StreamInfo: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + }; + + // Use atomic compare_and_swap operation + match db.compare_and_swap( + &stream_id, + None as Option<&[u8]>, + Some(info_bytes.as_slice()), + ) { + Ok(_) => { + // Force an immediate sync to disk + match db.flush_async().await { + Ok(_) => { + // Verify the write by attempting to read it back + match db.get(&stream_id) { + Ok(Some(_)) => { + let stream_url = format!("/webhooks/{}", stream_id); + tracing::info!("Successfully created and verified stream URL: {}", stream_url); + Json(WebhookPostResponse { stream_url }).into_response() + }, + Ok(None) => { + tracing::error!("Failed to verify stream creation: {}", stream_id); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + }, + Err(e) => { + tracing::error!("Error verifying stream creation: {}", e); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + } + }, + Err(e) => { + tracing::error!("Failed to flush DB: {}", e); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + } + } + Err(e) => { + tracing::error!("Failed to insert stream info: {}", e); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..18c3a5c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,43 @@ +// src/main.rs +use crate::config::AppConfig; +use crate::routes::create_router; +use crate::setup::init_logging; + +mod config; +mod routes; +mod setup; +mod handlers; +mod agents; +mod genaiscript; +mod utils; +mod session_identify; + +#[tokio::main] +async fn main() { + // Initialize logging + init_logging(); + + // Load configuration + let config = AppConfig::new(); + + // Create router with all routes + let app = create_router(); + + // Start core + let addr = "0.0.0.0:3006"; + tracing::info!("Attempting to bind core to {}", addr); + + let listener = match tokio::net::TcpListener::bind(addr).await { + Ok(l) => { + tracing::info!("Successfully bound to {}", l.local_addr().unwrap()); + l + } + Err(e) => { + tracing::error!("Failed to bind to {}: {}", addr, e); + panic!("Server failed to start"); + } + }; + + tracing::info!("Server starting on {}", listener.local_addr().unwrap()); + axum::serve(listener, app.into_make_service()).await.unwrap(); +} diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 0000000..b6dc988 --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,105 @@ +use crate::handlers::webhooks::handle_webhooks_post; +use crate::handlers::{ + error::handle_not_found, + ui::serve_ui + , + webhooks::handle_webhooks, +}; +use crate::session_identify::session_identify; +use axum::extract::Request; +use axum::response::Response; +use axum::routing::post; +// src/routes.rs +use axum::routing::{get, Router}; +use http::header::AUTHORIZATION; +use http::StatusCode; +use serde::{Deserialize, Serialize}; +use serde_json::Number; +use std::fmt; +use tower_http::trace::{self, TraceLayer}; +use tracing::Level; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CurrentUser { + pub(crate) sub: String, + pub name: String, + pub email: String, + pub exp: Number, + pub id: String, + pub aud: String, +} + +impl fmt::Display for CurrentUser { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "CurrentUser {{ id: {}, name: {}, email: {}, sub: {}, aud: {}, exp: {} }}", + self.id, self.name, self.email, self.sub, self.aud, self.exp + ) + } +} + +pub fn create_router() -> Router { + + Router::new() + .route("/", get(serve_ui)) + // request a stream resource + .route("/api/webhooks", post(handle_webhooks_post)) + // consume a stream resource + .route("/webhooks/:stream_id", get(handle_webhooks)) + .route_layer(axum::middleware::from_fn(auth)) + .route("/health", get(health)) + .layer( + TraceLayer::new_for_http() + .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO)) + .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), + ) + // left for smoke testing + // .route("/api/status", get(handle_status)) + .fallback(handle_not_found) +} + +async fn health() -> String { + return "ok".to_string(); +} + +async fn auth(mut req: Request, next: axum::middleware::Next) -> Result { + let session_token_header = req + .headers() + .get(AUTHORIZATION) + .and_then(|header_value| header_value.to_str().ok()); + + let session_token_parts= session_token_header.expect("No credentials").split(" ").collect::>(); + + let session_token = session_token_parts.get(1); + + + // log::info!("session_token: {:?}", session_token); + + let session_token = session_token.expect("Unauthorized: No credentials supplied"); + + let result = + if let Some(current_user) = authorize_current_user(&*session_token).await { + // info!("current user: {}", current_user); + // insert the current user into a request extension so the handler can + // extract it + req.extensions_mut().insert(current_user); + Ok(next.run(req).await) + } else { + Err(StatusCode::UNAUTHORIZED) + }; + result +} + + +async fn authorize_current_user( + session_token: &str, +) -> Option { + let session_identity = session_identify(session_token) + .await + .unwrap(); + + // println!("current_user: {:?}", session_identity.user); + + Some(serde_json::from_value::(session_identity.user).unwrap()) +} \ No newline at end of file diff --git a/src/session_identify.rs b/src/session_identify.rs new file mode 100644 index 0000000..a70a8fd --- /dev/null +++ b/src/session_identify.rs @@ -0,0 +1,55 @@ +use anyhow::Result; +use serde_json::Value; +use serde_json::json; +use base64::Engine; +use fips204::ml_dsa_44::{PrivateKey, PublicKey}; +use fips204::traits::{SerDes, Signer, Verifier}; +use crate::utils::base64::B64_ENCODER; + +pub struct SessionIdentity { + pub message: String, + pub signature: String, + pub target: String, + pub session_id: String, + pub user: Value +} + +pub async fn session_identify(session_token: &str) -> Result { + let session_data_base64 = session_token.split('.').nth(0).ok_or_else(|| anyhow::anyhow!("Invalid session data format"))?; + // println!("session_data_base64: {}", session_data_base64); + let session_data: Value = serde_json::de::from_slice(&*B64_ENCODER.b64_decode_payload(session_data_base64).map_err(|e| anyhow::anyhow!("Failed to decode session data: {}", e))?).map_err(|e| anyhow::anyhow!("Failed to parse session data: {}", e))?; + // println!("session_data: {:?}", session_data); + + + let signature_base64 = session_token.split('.').nth(1).ok_or_else(|| anyhow::anyhow!("Invalid session token format"))?; + // println!("signature_base64: {}", signature_base64); + + let target = session_data.get("aud") + .and_then(|e| e.as_str()) + .ok_or_else(|| anyhow::anyhow!("Session data missing audience"))?; + + let target = target.parse::().map_err(|e| anyhow::anyhow!("Failed to parse target to String: {}", e))?; + + let session_id = session_data.get("id") + .and_then(|e| e.as_str()) + .ok_or_else(|| anyhow::anyhow!("Session data missing id"))?; + + let session_id = session_id.parse::().map_err(|e| anyhow::anyhow!("Failed to parse session_id to String: {}", e))?; + + // let request_payload: Value = json!({ + // "message": session_data_base64, + // "signature": signature_base64, + // "target": target, + // "session_id": session_id, + // }); + + let result = SessionIdentity { + message: session_data_base64.to_string(), + signature: signature_base64.to_string(), + target, + session_id, + user: session_data.clone() + }; + + Ok(result) +} \ No newline at end of file diff --git a/src/setup.rs b/src/setup.rs new file mode 100644 index 0000000..ddc7ad8 --- /dev/null +++ b/src/setup.rs @@ -0,0 +1,10 @@ +// src/setup.rs +pub fn init_logging() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .with_target(true) + .with_thread_ids(true) + .with_file(true) + .with_line_number(true) + .init(); +} \ No newline at end of file diff --git a/src/utils/base64.rs b/src/utils/base64.rs new file mode 100644 index 0000000..518db8a --- /dev/null +++ b/src/utils/base64.rs @@ -0,0 +1,65 @@ +use base64::Engine; +use base64::engine::GeneralPurpose; +use base64::engine::general_purpose::STANDARD; +use base64::engine::general_purpose::STANDARD_NO_PAD; + +pub struct Base64Encoder { + payload_engine: GeneralPurpose, + signature_engine: GeneralPurpose, + public_key_engine: GeneralPurpose, + secret_key_engine: GeneralPurpose, +} + +impl Base64Encoder { + pub(crate) fn b64_encode(&self, p0: &[u8]) -> String { + self.payload_engine.encode(p0) + } + pub(crate) fn b64_decode(&self, p0: String) -> Result, base64::DecodeError> { + self.payload_engine.decode(p0) + } +} + +pub const B64_ENCODER: &Base64Encoder = &Base64Encoder::new(); + +impl Base64Encoder { + pub const fn new() -> Self { // Made new() a const fn + Base64Encoder { + payload_engine: STANDARD, + signature_engine: STANDARD, + public_key_engine: STANDARD, + secret_key_engine: STANDARD, + } + } + + pub fn b64_encode_payload>(&self, input: T) -> String { // Added trait bound + self.payload_engine.encode(input) + } + + pub fn b64_decode_payload>(&self, input: T) -> Result, base64::DecodeError> { // Added trait bound + self.payload_engine.decode(input) + } + + pub fn b64_decode_signature>(&self, input: T) -> Result, base64::DecodeError> { // Added trait bound + self.signature_engine.decode(input) + } + + pub fn b64_encode_signature>(&self, input: T) -> String { // Added trait bound + self.signature_engine.encode(input) + } + + pub fn b64_encode_public_key>(&self, input: T) -> String { // Added trait bound + self.public_key_engine.encode(input) + } + + pub fn b64_decode_public_key>(&self, input: T) -> Result, base64::DecodeError> { // Added trait bound + self.public_key_engine.decode(input) + } + + pub fn b64_encode_secret_key>(&self, input: T) -> String { // Added trait bound + self.secret_key_engine.encode(input) + } + + pub fn b64_decode_secret_key>(&self, input: T) -> Result, base64::DecodeError> { // Added trait bound + self.secret_key_engine.decode(input) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..021524b --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod utils; +pub mod base64; diff --git a/src/utils/utils.rs b/src/utils/utils.rs new file mode 100644 index 0000000..5e2b2ab --- /dev/null +++ b/src/utils/utils.rs @@ -0,0 +1,80 @@ +// utils.rs +use tokio::process::{Child, Command}; // Use tokio::process::Child and Command +use std::env; +use tokio::time::{timeout, Duration}; +use tracing; + + +pub struct ShimBinding { + user_input: String, + file_path: String, // Add new field for the file path + openai_api_key: String, + openai_api_base: String, + bing_search_api_key: String, + perigon_api_key: String, + tavily_api_key: String, + genaiscript_model_large: String, + genaiscript_model_small: String, + searxng_api_base_url: String, + searxng_password: String, +} + +impl ShimBinding { + pub fn new(user_input: String, file_path: String) -> Self { // Update constructor to take file path + Self { + user_input, + file_path, // Initialize the new field + openai_api_key: env::var("OPENAI_API_KEY").unwrap_or_default(), + openai_api_base: env::var("OPENAI_API_BASE").unwrap_or_default(), + bing_search_api_key: env::var("BING_SEARCH_API_KEY").unwrap_or_default(), + tavily_api_key: env::var("TAVILY_API_KEY").unwrap_or_default(), + genaiscript_model_large: env::var("GENAISCRIPT_MODEL_LARGE").unwrap_or_default(), + genaiscript_model_small: env::var("GENAISCRIPT_MODEL_SMALL").unwrap_or_default(), + perigon_api_key: env::var("PERIGON_API_KEY").unwrap_or_default(), + searxng_api_base_url: env::var("SEARXNG_API_BASE_URL").unwrap_or_default(), + searxng_password: env::var("SEARXNG_PASSWORD").unwrap_or_default(), + } + } + + pub fn execute(&self) -> std::io::Result { + let mut command = Command::new("./dist/genaiscript-rust-shim.js"); + command + .arg("--file") + .arg(&self.file_path) // Use the file_path field instead of hardcoded value + .arg(format!("USER_INPUT={}", self.user_input)) + .env("OPENAI_API_KEY", &self.openai_api_key) + .env("OPENAI_API_BASE", &self.openai_api_base) + .env("BING_SEARCH_API_KEY", &self.bing_search_api_key) + .env("TAVILY_API_KEY", &self.tavily_api_key) + .env("GENAISCRIPT_MODEL_LARGE", &self.genaiscript_model_large) + .env("GENAISCRIPT_MODEL_SMALL", &self.genaiscript_model_small) + .env("PERIGON_API_KEY", &self.perigon_api_key) + .env("SEARXNG_API_BASE_URL", &self.searxng_api_base_url) + .env("SEARXNG_PASSWORD", &self.searxng_password) + .stdout(std::process::Stdio::piped()) // Use tokio::io::Stdio::piped() + .stderr(std::process::Stdio::inherit()); // Use tokio::io::Stdio::piped() + + command.spawn() + } +} + + + /// Generic helper to execute a ShimBinding-based agent with a timeout +pub async fn run_agent(stream_id: &str, input: &str, file_path: &str) -> Result { + tracing::debug!("Initiating agent for stream {} with file path {}", stream_id, file_path); + + let shim_binding = ShimBinding::new(input.to_string(), file_path.to_string()); + let spawn_future = async move { + match shim_binding.execute() { + Ok(child) => Ok(child), + Err(e) => { + tracing::error!("Failed to spawn shim process: {}", e); + Err(e.to_string()) + } + } + }; + + timeout(Duration::from_secs(10), spawn_future) + .await + .unwrap_or_else(|_| Err("Command timed out after 10 seconds".to_string())) +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..54f4023 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", +// "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": [ + "./packages/genaiscript/genaisrc/genaiscript.d.ts" + ] +}