@specstream/langchain 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SpecStream SAS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # @specstream/langchain
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [SpecStream](https://specstream.ai) is the live web data engine for teams and agents. One platform to build, search, extract, enrich, map, and monitor data across the entire web.
4
+
5
+ Ask for any dataset in plain words, and SpecStream builds it from the entire live web: every match found, extracted and enriched into structured records, then monitored for change. It runs through the API and MCP, and inside Sheets, CRMs, Slack, n8n, Zapier and the other tools teams already use.
6
+
7
+ Source URL and read time on every value.
8
+
9
+ Three products:
10
+
11
+ 1. Search API: `GET /v1/web-search`, $5 per 1,000 searches. The most accurate web search API in the world (scores and method: https://specstream.ai/benchmarks).
12
+ 2. Extract API: `POST /v1/extract`, up to 20 URLs a call (web pages, PDF, Word, Excel), $0.80 per 1,000 pages. A page with no text is not charged.
13
+ 3. Deep Search: Ask in plain words. Get every match on the live web, in structured rows. Priced per row.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @specstream/langchain @langchain/core
19
+ export SPECSTREAM_API_KEY=ss_live_... # Agents & API in your SpecStream workspace
20
+ ```
21
+
22
+ A free account comes with $10 of credit to use within 30 days and needs no card: [specstream.ai](https://specstream.ai/signup).
23
+
24
+ ## Tools
25
+
26
+ Both tools are LangChain `StructuredTool` classes, built like `@langchain/tavily`.
27
+
28
+ | Tool | Name | What it does | Cost |
29
+ | --- | --- | --- | --- |
30
+ | `SpecStreamSearch` | `specstream_search` | Calls `GET /v1/web-search` and returns the answer, matching rows and ranked sources, with page passages when `passages: true` | $5 per 1,000 searches |
31
+ | `SpecStreamExtract` | `specstream_extract` | Calls `POST /v1/extract` and returns the main text, title and published date of 1 to 20 web pages, PDF, Word or Excel files | $0.80 per 1,000 pages (a page with no text is free) |
32
+
33
+ Each tool takes `specstreamApiKey` (default: the `SPECSTREAM_API_KEY` environment variable), `apiBaseUrl`, `name` and `description`. A failed call returns `{ error }` to the model.
34
+
35
+ MIT licensed.
@@ -0,0 +1,6 @@
1
+ export type { SpecStreamSearchAPIRetrieverFields } from "./specstream-search.js";
2
+ export { SpecStreamSearch } from "./specstream-search.js";
3
+ export type { SpecStreamExtractAPIRetrieverFields } from "./specstream-extract.js";
4
+ export { SpecStreamExtract } from "./specstream-extract.js";
5
+ export type { SpecStreamAPIWrapperParams, SpecStreamExtractParams, SpecStreamExtractResponse, SpecStreamExtractResult, SpecStreamSearchParams, SpecStreamSearchResponse, SpecStreamSource, } from "./utils.js";
6
+ export { SpecStreamSearchAPIWrapper, SpecStreamExtractAPIWrapper } from "./utils.js";
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpecStreamExtractAPIWrapper = exports.SpecStreamSearchAPIWrapper = exports.SpecStreamExtract = exports.SpecStreamSearch = void 0;
4
+ var specstream_search_js_1 = require("./specstream-search.js");
5
+ Object.defineProperty(exports, "SpecStreamSearch", { enumerable: true, get: function () { return specstream_search_js_1.SpecStreamSearch; } });
6
+ var specstream_extract_js_1 = require("./specstream-extract.js");
7
+ Object.defineProperty(exports, "SpecStreamExtract", { enumerable: true, get: function () { return specstream_extract_js_1.SpecStreamExtract; } });
8
+ var utils_js_1 = require("./utils.js");
9
+ Object.defineProperty(exports, "SpecStreamSearchAPIWrapper", { enumerable: true, get: function () { return utils_js_1.SpecStreamSearchAPIWrapper; } });
10
+ Object.defineProperty(exports, "SpecStreamExtractAPIWrapper", { enumerable: true, get: function () { return utils_js_1.SpecStreamExtractAPIWrapper; } });
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,61 @@
1
+ import { z } from "zod/v3";
2
+ import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
3
+ import { StructuredTool, ToolParams } from "@langchain/core/tools";
4
+ import { InferInteropZodOutput } from "@langchain/core/utils/types";
5
+ import { SpecStreamExtractAPIWrapper, type SpecStreamExtractResponse } from "./utils.js";
6
+ /**
7
+ * Options for the SpecStreamExtract tool.
8
+ */
9
+ export type SpecStreamExtractAPIRetrieverFields = ToolParams & {
10
+ /** The base URL of the SpecStream API. @default "https://api.specstream.ai" */
11
+ apiBaseUrl?: string;
12
+ /** The API key. Defaults to the `SPECSTREAM_API_KEY` environment variable. */
13
+ specstreamApiKey?: string;
14
+ /** Output format of the page text. Takes precedence over the call parameter. */
15
+ format?: "markdown" | "text";
16
+ /** Replace the tool name. */
17
+ name?: string;
18
+ /** Replace the tool description. */
19
+ description?: string;
20
+ /** Use a custom API wrapper, for example in tests. */
21
+ apiWrapper?: SpecStreamExtractAPIWrapper;
22
+ };
23
+ declare const inputSchema: z.ZodObject<{
24
+ urls: z.ZodArray<z.ZodString, "many">;
25
+ format: z.ZodOptional<z.ZodEnum<["markdown", "text"]>>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ urls: string[];
28
+ format?: "markdown" | "text" | undefined;
29
+ }, {
30
+ urls: string[];
31
+ format?: "markdown" | "text" | undefined;
32
+ }>;
33
+ /**
34
+ * A tool that reads the main text of web pages and documents live with the
35
+ * SpecStream Extract API.
36
+ *
37
+ * Authentication is handled via an API key, passed at construction or set as
38
+ * the environment variable `SPECSTREAM_API_KEY`.
39
+ */
40
+ export declare class SpecStreamExtract extends StructuredTool<typeof inputSchema> {
41
+ static lc_name(): string;
42
+ description: string;
43
+ name: string;
44
+ schema: z.ZodObject<{
45
+ urls: z.ZodArray<z.ZodString, "many">;
46
+ format: z.ZodOptional<z.ZodEnum<["markdown", "text"]>>;
47
+ }, "strip", z.ZodTypeAny, {
48
+ urls: string[];
49
+ format?: "markdown" | "text" | undefined;
50
+ }, {
51
+ urls: string[];
52
+ format?: "markdown" | "text" | undefined;
53
+ }>;
54
+ format?: "markdown" | "text";
55
+ private apiWrapper;
56
+ constructor(params?: SpecStreamExtractAPIRetrieverFields);
57
+ _call(input: InferInteropZodOutput<typeof inputSchema>, _runManager?: CallbackManagerForToolRun): Promise<SpecStreamExtractResponse | {
58
+ error: string;
59
+ }>;
60
+ }
61
+ export {};
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpecStreamExtract = void 0;
4
+ const v3_1 = require("zod/v3");
5
+ const tools_1 = require("@langchain/core/tools");
6
+ const utils_js_1 = require("./utils.js");
7
+ const inputSchema = v3_1.z.object({
8
+ urls: v3_1.z
9
+ .array(v3_1.z.string().min(1))
10
+ .min(1)
11
+ .max(20)
12
+ .describe("1 to 20 http or https URLs of web pages, PDF, Word or Excel files to read."),
13
+ format: v3_1.z
14
+ .enum(["markdown", "text"])
15
+ .optional()
16
+ .describe('The format of the page text: "markdown" (default) keeps headings, lists and links; "text" is plain text.'),
17
+ });
18
+ /**
19
+ * A tool that reads the main text of web pages and documents live with the
20
+ * SpecStream Extract API.
21
+ *
22
+ * Authentication is handled via an API key, passed at construction or set as
23
+ * the environment variable `SPECSTREAM_API_KEY`.
24
+ */
25
+ class SpecStreamExtract extends tools_1.StructuredTool {
26
+ static lc_name() {
27
+ return "SpecStreamExtract";
28
+ }
29
+ description = "Read the main text, title and published date of up to 20 web pages, PDF, Word or Excel files live with the SpecStream Extract API. Input should be a list of URLs.";
30
+ name = "specstream_extract";
31
+ schema = inputSchema;
32
+ format;
33
+ apiWrapper;
34
+ constructor(params = {}) {
35
+ super(params);
36
+ if (params.name) {
37
+ this.name = params.name;
38
+ }
39
+ if (params.description) {
40
+ this.description = params.description;
41
+ }
42
+ this.format = params.format;
43
+ this.apiWrapper =
44
+ params.apiWrapper ??
45
+ new utils_js_1.SpecStreamExtractAPIWrapper({
46
+ specstreamApiKey: params.specstreamApiKey,
47
+ apiBaseUrl: params.apiBaseUrl,
48
+ });
49
+ }
50
+ async _call(input, _runManager) {
51
+ try {
52
+ return await this.apiWrapper.rawResults({
53
+ urls: input.urls,
54
+ format: this.format ?? input.format,
55
+ });
56
+ }
57
+ catch (e) {
58
+ return { error: e instanceof Error ? e.message : String(e) };
59
+ }
60
+ }
61
+ }
62
+ exports.SpecStreamExtract = SpecStreamExtract;
@@ -0,0 +1,55 @@
1
+ import { z } from "zod/v3";
2
+ import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
3
+ import { StructuredTool, ToolParams } from "@langchain/core/tools";
4
+ import { InferInteropZodOutput } from "@langchain/core/utils/types";
5
+ import { SpecStreamSearchAPIWrapper, type SpecStreamSearchResponse } from "./utils.js";
6
+ /**
7
+ * Options for the SpecStreamSearch tool.
8
+ */
9
+ export type SpecStreamSearchAPIRetrieverFields = ToolParams & {
10
+ /** The base URL of the SpecStream API. @default "https://api.specstream.ai" */
11
+ apiBaseUrl?: string;
12
+ /** The API key. Defaults to the `SPECSTREAM_API_KEY` environment variable. */
13
+ specstreamApiKey?: string;
14
+ /** Add the text of the top pages to every search. Takes up to about a second more. @default false */
15
+ passages?: boolean;
16
+ /** Replace the tool name. */
17
+ name?: string;
18
+ /** Replace the tool description. */
19
+ description?: string;
20
+ /** Use a custom API wrapper, for example in tests. */
21
+ apiWrapper?: SpecStreamSearchAPIWrapper;
22
+ };
23
+ declare const inputSchema: z.ZodObject<{
24
+ query: z.ZodString;
25
+ }, "strip", z.ZodTypeAny, {
26
+ query: string;
27
+ }, {
28
+ query: string;
29
+ }>;
30
+ /**
31
+ * A tool that searches the live web with the SpecStream Search API and
32
+ * returns the answer, matching rows and ranked sources.
33
+ *
34
+ * Authentication is handled via an API key, passed at construction or set as
35
+ * the environment variable `SPECSTREAM_API_KEY`.
36
+ */
37
+ export declare class SpecStreamSearch extends StructuredTool<typeof inputSchema> {
38
+ static lc_name(): string;
39
+ description: string;
40
+ name: string;
41
+ schema: z.ZodObject<{
42
+ query: z.ZodString;
43
+ }, "strip", z.ZodTypeAny, {
44
+ query: string;
45
+ }, {
46
+ query: string;
47
+ }>;
48
+ passages?: boolean;
49
+ private apiWrapper;
50
+ constructor(params?: SpecStreamSearchAPIRetrieverFields);
51
+ _call(input: InferInteropZodOutput<typeof inputSchema>, _runManager?: CallbackManagerForToolRun): Promise<SpecStreamSearchResponse | {
52
+ error: string;
53
+ }>;
54
+ }
55
+ export {};
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpecStreamSearch = void 0;
4
+ const v3_1 = require("zod/v3");
5
+ const tools_1 = require("@langchain/core/tools");
6
+ const utils_js_1 = require("./utils.js");
7
+ const inputSchema = v3_1.z.object({
8
+ query: v3_1.z
9
+ .string()
10
+ .min(1)
11
+ .max(2000)
12
+ .describe("The question or request in everyday words, 1 to 2,000 characters."),
13
+ });
14
+ /**
15
+ * A tool that searches the live web with the SpecStream Search API and
16
+ * returns the answer, matching rows and ranked sources.
17
+ *
18
+ * Authentication is handled via an API key, passed at construction or set as
19
+ * the environment variable `SPECSTREAM_API_KEY`.
20
+ */
21
+ class SpecStreamSearch extends tools_1.StructuredTool {
22
+ static lc_name() {
23
+ return "SpecStreamSearch";
24
+ }
25
+ description = "Search the live web with the SpecStream Search API. Returns the answer with the page it was read from, matching rows with their fields, and ranked sources. Input should be a question or request in everyday words.";
26
+ name = "specstream_search";
27
+ schema = inputSchema;
28
+ passages;
29
+ apiWrapper;
30
+ constructor(params = {}) {
31
+ super(params);
32
+ if (params.name) {
33
+ this.name = params.name;
34
+ }
35
+ if (params.description) {
36
+ this.description = params.description;
37
+ }
38
+ this.passages = params.passages;
39
+ this.apiWrapper =
40
+ params.apiWrapper ??
41
+ new utils_js_1.SpecStreamSearchAPIWrapper({
42
+ specstreamApiKey: params.specstreamApiKey,
43
+ apiBaseUrl: params.apiBaseUrl,
44
+ });
45
+ }
46
+ async _call(input, _runManager) {
47
+ try {
48
+ return await this.apiWrapper.rawResults({ q: input.query, passages: this.passages });
49
+ }
50
+ catch (e) {
51
+ return { error: e instanceof Error ? e.message : String(e) };
52
+ }
53
+ }
54
+ }
55
+ exports.SpecStreamSearch = SpecStreamSearch;
@@ -0,0 +1,90 @@
1
+ export declare const SPECSTREAM_BASE_URL = "https://api.specstream.ai";
2
+ export declare const MAX_EXTRACT_URLS = 20;
3
+ export type SpecStreamSearchParams = {
4
+ /** The question or request in everyday words, 1 to 2,000 characters. */
5
+ q: string;
6
+ /** Add the text of the top pages to the reply. */
7
+ passages?: boolean;
8
+ };
9
+ export type SpecStreamSource = {
10
+ url: string;
11
+ title?: string;
12
+ site?: string;
13
+ snippet?: string;
14
+ published?: string;
15
+ cited?: boolean;
16
+ };
17
+ export type SpecStreamSearchResponse = {
18
+ id?: string;
19
+ request_id?: string;
20
+ kind: "fact" | "list";
21
+ query?: string;
22
+ columns?: unknown[];
23
+ answer: {
24
+ value: string;
25
+ page: string;
26
+ seen_at?: string;
27
+ } | null;
28
+ rows: Array<{
29
+ name?: string;
30
+ page?: string;
31
+ snippet?: string;
32
+ probability?: number;
33
+ sources?: string[];
34
+ fields?: Record<string, unknown>;
35
+ }>;
36
+ sources: SpecStreamSource[];
37
+ passages?: Array<{
38
+ url: string;
39
+ title?: string;
40
+ text: string;
41
+ }>;
42
+ handoff?: Record<string, unknown>;
43
+ took_ms?: number;
44
+ price_usd?: number;
45
+ };
46
+ export type SpecStreamExtractParams = {
47
+ /** 1 to 20 http or https URLs. */
48
+ urls: string[];
49
+ format?: "markdown" | "text";
50
+ };
51
+ export type SpecStreamExtractResult = {
52
+ url: string;
53
+ status: "ok" | "blocked" | "not_found" | "empty" | "unsupported" | "invalid_url" | "failed";
54
+ text?: string;
55
+ title?: string;
56
+ published?: string;
57
+ read_from?: "live" | "site" | "archive";
58
+ captured_at?: string;
59
+ };
60
+ export type SpecStreamExtractResponse = {
61
+ results: SpecStreamExtractResult[];
62
+ request_id?: string;
63
+ took_ms?: number;
64
+ price_usd?: number;
65
+ };
66
+ export type SpecStreamAPIWrapperParams = {
67
+ specstreamApiKey?: string;
68
+ apiBaseUrl?: string;
69
+ };
70
+ declare abstract class BaseSpecStreamAPIWrapper {
71
+ protected specstreamApiKey: string;
72
+ protected apiBaseUrl: string;
73
+ constructor(params?: SpecStreamAPIWrapperParams);
74
+ protected call<T>(method: "GET" | "POST", path: string, body?: Record<string, unknown>): Promise<T>;
75
+ }
76
+ /**
77
+ * A wrapper that encapsulates access to the SpecStream Search API
78
+ * (`GET /v1/web-search`). Primarily used for testing.
79
+ */
80
+ export declare class SpecStreamSearchAPIWrapper extends BaseSpecStreamAPIWrapper {
81
+ rawResults(params: SpecStreamSearchParams): Promise<SpecStreamSearchResponse>;
82
+ }
83
+ /**
84
+ * A wrapper that encapsulates access to the SpecStream Extract API
85
+ * (`POST /v1/extract`). Primarily used for testing.
86
+ */
87
+ export declare class SpecStreamExtractAPIWrapper extends BaseSpecStreamAPIWrapper {
88
+ rawResults(params: SpecStreamExtractParams): Promise<SpecStreamExtractResponse>;
89
+ }
90
+ export {};
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SpecStreamExtractAPIWrapper = exports.SpecStreamSearchAPIWrapper = exports.MAX_EXTRACT_URLS = exports.SPECSTREAM_BASE_URL = void 0;
4
+ const env_1 = require("@langchain/core/utils/env");
5
+ exports.SPECSTREAM_BASE_URL = "https://api.specstream.ai";
6
+ exports.MAX_EXTRACT_URLS = 20;
7
+ class BaseSpecStreamAPIWrapper {
8
+ specstreamApiKey;
9
+ apiBaseUrl;
10
+ constructor(params = {}) {
11
+ const apiKey = (params.specstreamApiKey ?? (0, env_1.getEnvironmentVariable)("SPECSTREAM_API_KEY") ?? "").trim();
12
+ if (!apiKey) {
13
+ throw new Error("No SpecStream API key found. Pass specstreamApiKey or set SPECSTREAM_API_KEY. Create a key under Agents & API in your SpecStream workspace.");
14
+ }
15
+ this.specstreamApiKey = apiKey.replace(/^bearer\s+/i, "");
16
+ this.apiBaseUrl = (params.apiBaseUrl ?? (0, env_1.getEnvironmentVariable)("SPECSTREAM_BASE_URL") ?? exports.SPECSTREAM_BASE_URL).replace(/\/+$/, "");
17
+ }
18
+ async call(method, path, body) {
19
+ const response = await fetch(`${this.apiBaseUrl}${path}`, {
20
+ method,
21
+ headers: {
22
+ Authorization: `Bearer ${this.specstreamApiKey}`,
23
+ Accept: "application/json",
24
+ ...(body === undefined ? {} : { "Content-Type": "application/json" }),
25
+ "User-Agent": "@specstream/langchain",
26
+ },
27
+ body: body === undefined ? undefined : JSON.stringify(body),
28
+ });
29
+ let payload = {};
30
+ try {
31
+ payload = await response.json();
32
+ }
33
+ catch {
34
+ // A reply that is not JSON is reported by its status below.
35
+ }
36
+ if (!response.ok || !payload?.data) {
37
+ throw new Error(`Error ${response.status}: ${payload?.error?.message ?? "Unknown error"}`);
38
+ }
39
+ return payload.data;
40
+ }
41
+ }
42
+ /**
43
+ * A wrapper that encapsulates access to the SpecStream Search API
44
+ * (`GET /v1/web-search`). Primarily used for testing.
45
+ */
46
+ class SpecStreamSearchAPIWrapper extends BaseSpecStreamAPIWrapper {
47
+ async rawResults(params) {
48
+ const query = new URLSearchParams({ q: params.q.trim() });
49
+ if (params.passages) {
50
+ query.set("passages", "true");
51
+ }
52
+ return this.call("GET", `/v1/web-search?${query.toString()}`);
53
+ }
54
+ }
55
+ exports.SpecStreamSearchAPIWrapper = SpecStreamSearchAPIWrapper;
56
+ /**
57
+ * A wrapper that encapsulates access to the SpecStream Extract API
58
+ * (`POST /v1/extract`). Primarily used for testing.
59
+ */
60
+ class SpecStreamExtractAPIWrapper extends BaseSpecStreamAPIWrapper {
61
+ async rawResults(params) {
62
+ const urls = params.urls.map((url) => url.trim()).filter(Boolean).slice(0, exports.MAX_EXTRACT_URLS);
63
+ return this.call("POST", "/v1/extract", {
64
+ urls,
65
+ format: params.format === "text" ? "text" : "markdown",
66
+ });
67
+ }
68
+ }
69
+ exports.SpecStreamExtractAPIWrapper = SpecStreamExtractAPIWrapper;
@@ -0,0 +1,6 @@
1
+ export type { SpecStreamSearchAPIRetrieverFields } from "./specstream-search.js";
2
+ export { SpecStreamSearch } from "./specstream-search.js";
3
+ export type { SpecStreamExtractAPIRetrieverFields } from "./specstream-extract.js";
4
+ export { SpecStreamExtract } from "./specstream-extract.js";
5
+ export type { SpecStreamAPIWrapperParams, SpecStreamExtractParams, SpecStreamExtractResponse, SpecStreamExtractResult, SpecStreamSearchParams, SpecStreamSearchResponse, SpecStreamSource, } from "./utils.js";
6
+ export { SpecStreamSearchAPIWrapper, SpecStreamExtractAPIWrapper } from "./utils.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { SpecStreamSearch } from "./specstream-search.js";
2
+ export { SpecStreamExtract } from "./specstream-extract.js";
3
+ export { SpecStreamSearchAPIWrapper, SpecStreamExtractAPIWrapper } from "./utils.js";
@@ -0,0 +1,61 @@
1
+ import { z } from "zod/v3";
2
+ import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
3
+ import { StructuredTool, ToolParams } from "@langchain/core/tools";
4
+ import { InferInteropZodOutput } from "@langchain/core/utils/types";
5
+ import { SpecStreamExtractAPIWrapper, type SpecStreamExtractResponse } from "./utils.js";
6
+ /**
7
+ * Options for the SpecStreamExtract tool.
8
+ */
9
+ export type SpecStreamExtractAPIRetrieverFields = ToolParams & {
10
+ /** The base URL of the SpecStream API. @default "https://api.specstream.ai" */
11
+ apiBaseUrl?: string;
12
+ /** The API key. Defaults to the `SPECSTREAM_API_KEY` environment variable. */
13
+ specstreamApiKey?: string;
14
+ /** Output format of the page text. Takes precedence over the call parameter. */
15
+ format?: "markdown" | "text";
16
+ /** Replace the tool name. */
17
+ name?: string;
18
+ /** Replace the tool description. */
19
+ description?: string;
20
+ /** Use a custom API wrapper, for example in tests. */
21
+ apiWrapper?: SpecStreamExtractAPIWrapper;
22
+ };
23
+ declare const inputSchema: z.ZodObject<{
24
+ urls: z.ZodArray<z.ZodString, "many">;
25
+ format: z.ZodOptional<z.ZodEnum<["markdown", "text"]>>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ urls: string[];
28
+ format?: "markdown" | "text" | undefined;
29
+ }, {
30
+ urls: string[];
31
+ format?: "markdown" | "text" | undefined;
32
+ }>;
33
+ /**
34
+ * A tool that reads the main text of web pages and documents live with the
35
+ * SpecStream Extract API.
36
+ *
37
+ * Authentication is handled via an API key, passed at construction or set as
38
+ * the environment variable `SPECSTREAM_API_KEY`.
39
+ */
40
+ export declare class SpecStreamExtract extends StructuredTool<typeof inputSchema> {
41
+ static lc_name(): string;
42
+ description: string;
43
+ name: string;
44
+ schema: z.ZodObject<{
45
+ urls: z.ZodArray<z.ZodString, "many">;
46
+ format: z.ZodOptional<z.ZodEnum<["markdown", "text"]>>;
47
+ }, "strip", z.ZodTypeAny, {
48
+ urls: string[];
49
+ format?: "markdown" | "text" | undefined;
50
+ }, {
51
+ urls: string[];
52
+ format?: "markdown" | "text" | undefined;
53
+ }>;
54
+ format?: "markdown" | "text";
55
+ private apiWrapper;
56
+ constructor(params?: SpecStreamExtractAPIRetrieverFields);
57
+ _call(input: InferInteropZodOutput<typeof inputSchema>, _runManager?: CallbackManagerForToolRun): Promise<SpecStreamExtractResponse | {
58
+ error: string;
59
+ }>;
60
+ }
61
+ export {};
@@ -0,0 +1,58 @@
1
+ import { z } from "zod/v3";
2
+ import { StructuredTool } from "@langchain/core/tools";
3
+ import { SpecStreamExtractAPIWrapper } from "./utils.js";
4
+ const inputSchema = z.object({
5
+ urls: z
6
+ .array(z.string().min(1))
7
+ .min(1)
8
+ .max(20)
9
+ .describe("1 to 20 http or https URLs of web pages, PDF, Word or Excel files to read."),
10
+ format: z
11
+ .enum(["markdown", "text"])
12
+ .optional()
13
+ .describe('The format of the page text: "markdown" (default) keeps headings, lists and links; "text" is plain text.'),
14
+ });
15
+ /**
16
+ * A tool that reads the main text of web pages and documents live with the
17
+ * SpecStream Extract API.
18
+ *
19
+ * Authentication is handled via an API key, passed at construction or set as
20
+ * the environment variable `SPECSTREAM_API_KEY`.
21
+ */
22
+ export class SpecStreamExtract extends StructuredTool {
23
+ static lc_name() {
24
+ return "SpecStreamExtract";
25
+ }
26
+ description = "Read the main text, title and published date of up to 20 web pages, PDF, Word or Excel files live with the SpecStream Extract API. Input should be a list of URLs.";
27
+ name = "specstream_extract";
28
+ schema = inputSchema;
29
+ format;
30
+ apiWrapper;
31
+ constructor(params = {}) {
32
+ super(params);
33
+ if (params.name) {
34
+ this.name = params.name;
35
+ }
36
+ if (params.description) {
37
+ this.description = params.description;
38
+ }
39
+ this.format = params.format;
40
+ this.apiWrapper =
41
+ params.apiWrapper ??
42
+ new SpecStreamExtractAPIWrapper({
43
+ specstreamApiKey: params.specstreamApiKey,
44
+ apiBaseUrl: params.apiBaseUrl,
45
+ });
46
+ }
47
+ async _call(input, _runManager) {
48
+ try {
49
+ return await this.apiWrapper.rawResults({
50
+ urls: input.urls,
51
+ format: this.format ?? input.format,
52
+ });
53
+ }
54
+ catch (e) {
55
+ return { error: e instanceof Error ? e.message : String(e) };
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,55 @@
1
+ import { z } from "zod/v3";
2
+ import { CallbackManagerForToolRun } from "@langchain/core/callbacks/manager";
3
+ import { StructuredTool, ToolParams } from "@langchain/core/tools";
4
+ import { InferInteropZodOutput } from "@langchain/core/utils/types";
5
+ import { SpecStreamSearchAPIWrapper, type SpecStreamSearchResponse } from "./utils.js";
6
+ /**
7
+ * Options for the SpecStreamSearch tool.
8
+ */
9
+ export type SpecStreamSearchAPIRetrieverFields = ToolParams & {
10
+ /** The base URL of the SpecStream API. @default "https://api.specstream.ai" */
11
+ apiBaseUrl?: string;
12
+ /** The API key. Defaults to the `SPECSTREAM_API_KEY` environment variable. */
13
+ specstreamApiKey?: string;
14
+ /** Add the text of the top pages to every search. Takes up to about a second more. @default false */
15
+ passages?: boolean;
16
+ /** Replace the tool name. */
17
+ name?: string;
18
+ /** Replace the tool description. */
19
+ description?: string;
20
+ /** Use a custom API wrapper, for example in tests. */
21
+ apiWrapper?: SpecStreamSearchAPIWrapper;
22
+ };
23
+ declare const inputSchema: z.ZodObject<{
24
+ query: z.ZodString;
25
+ }, "strip", z.ZodTypeAny, {
26
+ query: string;
27
+ }, {
28
+ query: string;
29
+ }>;
30
+ /**
31
+ * A tool that searches the live web with the SpecStream Search API and
32
+ * returns the answer, matching rows and ranked sources.
33
+ *
34
+ * Authentication is handled via an API key, passed at construction or set as
35
+ * the environment variable `SPECSTREAM_API_KEY`.
36
+ */
37
+ export declare class SpecStreamSearch extends StructuredTool<typeof inputSchema> {
38
+ static lc_name(): string;
39
+ description: string;
40
+ name: string;
41
+ schema: z.ZodObject<{
42
+ query: z.ZodString;
43
+ }, "strip", z.ZodTypeAny, {
44
+ query: string;
45
+ }, {
46
+ query: string;
47
+ }>;
48
+ passages?: boolean;
49
+ private apiWrapper;
50
+ constructor(params?: SpecStreamSearchAPIRetrieverFields);
51
+ _call(input: InferInteropZodOutput<typeof inputSchema>, _runManager?: CallbackManagerForToolRun): Promise<SpecStreamSearchResponse | {
52
+ error: string;
53
+ }>;
54
+ }
55
+ export {};
@@ -0,0 +1,51 @@
1
+ import { z } from "zod/v3";
2
+ import { StructuredTool } from "@langchain/core/tools";
3
+ import { SpecStreamSearchAPIWrapper } from "./utils.js";
4
+ const inputSchema = z.object({
5
+ query: z
6
+ .string()
7
+ .min(1)
8
+ .max(2000)
9
+ .describe("The question or request in everyday words, 1 to 2,000 characters."),
10
+ });
11
+ /**
12
+ * A tool that searches the live web with the SpecStream Search API and
13
+ * returns the answer, matching rows and ranked sources.
14
+ *
15
+ * Authentication is handled via an API key, passed at construction or set as
16
+ * the environment variable `SPECSTREAM_API_KEY`.
17
+ */
18
+ export class SpecStreamSearch extends StructuredTool {
19
+ static lc_name() {
20
+ return "SpecStreamSearch";
21
+ }
22
+ description = "Search the live web with the SpecStream Search API. Returns the answer with the page it was read from, matching rows with their fields, and ranked sources. Input should be a question or request in everyday words.";
23
+ name = "specstream_search";
24
+ schema = inputSchema;
25
+ passages;
26
+ apiWrapper;
27
+ constructor(params = {}) {
28
+ super(params);
29
+ if (params.name) {
30
+ this.name = params.name;
31
+ }
32
+ if (params.description) {
33
+ this.description = params.description;
34
+ }
35
+ this.passages = params.passages;
36
+ this.apiWrapper =
37
+ params.apiWrapper ??
38
+ new SpecStreamSearchAPIWrapper({
39
+ specstreamApiKey: params.specstreamApiKey,
40
+ apiBaseUrl: params.apiBaseUrl,
41
+ });
42
+ }
43
+ async _call(input, _runManager) {
44
+ try {
45
+ return await this.apiWrapper.rawResults({ q: input.query, passages: this.passages });
46
+ }
47
+ catch (e) {
48
+ return { error: e instanceof Error ? e.message : String(e) };
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,90 @@
1
+ export declare const SPECSTREAM_BASE_URL = "https://api.specstream.ai";
2
+ export declare const MAX_EXTRACT_URLS = 20;
3
+ export type SpecStreamSearchParams = {
4
+ /** The question or request in everyday words, 1 to 2,000 characters. */
5
+ q: string;
6
+ /** Add the text of the top pages to the reply. */
7
+ passages?: boolean;
8
+ };
9
+ export type SpecStreamSource = {
10
+ url: string;
11
+ title?: string;
12
+ site?: string;
13
+ snippet?: string;
14
+ published?: string;
15
+ cited?: boolean;
16
+ };
17
+ export type SpecStreamSearchResponse = {
18
+ id?: string;
19
+ request_id?: string;
20
+ kind: "fact" | "list";
21
+ query?: string;
22
+ columns?: unknown[];
23
+ answer: {
24
+ value: string;
25
+ page: string;
26
+ seen_at?: string;
27
+ } | null;
28
+ rows: Array<{
29
+ name?: string;
30
+ page?: string;
31
+ snippet?: string;
32
+ probability?: number;
33
+ sources?: string[];
34
+ fields?: Record<string, unknown>;
35
+ }>;
36
+ sources: SpecStreamSource[];
37
+ passages?: Array<{
38
+ url: string;
39
+ title?: string;
40
+ text: string;
41
+ }>;
42
+ handoff?: Record<string, unknown>;
43
+ took_ms?: number;
44
+ price_usd?: number;
45
+ };
46
+ export type SpecStreamExtractParams = {
47
+ /** 1 to 20 http or https URLs. */
48
+ urls: string[];
49
+ format?: "markdown" | "text";
50
+ };
51
+ export type SpecStreamExtractResult = {
52
+ url: string;
53
+ status: "ok" | "blocked" | "not_found" | "empty" | "unsupported" | "invalid_url" | "failed";
54
+ text?: string;
55
+ title?: string;
56
+ published?: string;
57
+ read_from?: "live" | "site" | "archive";
58
+ captured_at?: string;
59
+ };
60
+ export type SpecStreamExtractResponse = {
61
+ results: SpecStreamExtractResult[];
62
+ request_id?: string;
63
+ took_ms?: number;
64
+ price_usd?: number;
65
+ };
66
+ export type SpecStreamAPIWrapperParams = {
67
+ specstreamApiKey?: string;
68
+ apiBaseUrl?: string;
69
+ };
70
+ declare abstract class BaseSpecStreamAPIWrapper {
71
+ protected specstreamApiKey: string;
72
+ protected apiBaseUrl: string;
73
+ constructor(params?: SpecStreamAPIWrapperParams);
74
+ protected call<T>(method: "GET" | "POST", path: string, body?: Record<string, unknown>): Promise<T>;
75
+ }
76
+ /**
77
+ * A wrapper that encapsulates access to the SpecStream Search API
78
+ * (`GET /v1/web-search`). Primarily used for testing.
79
+ */
80
+ export declare class SpecStreamSearchAPIWrapper extends BaseSpecStreamAPIWrapper {
81
+ rawResults(params: SpecStreamSearchParams): Promise<SpecStreamSearchResponse>;
82
+ }
83
+ /**
84
+ * A wrapper that encapsulates access to the SpecStream Extract API
85
+ * (`POST /v1/extract`). Primarily used for testing.
86
+ */
87
+ export declare class SpecStreamExtractAPIWrapper extends BaseSpecStreamAPIWrapper {
88
+ rawResults(params: SpecStreamExtractParams): Promise<SpecStreamExtractResponse>;
89
+ }
90
+ export {};
package/dist/utils.js ADDED
@@ -0,0 +1,64 @@
1
+ import { getEnvironmentVariable } from "@langchain/core/utils/env";
2
+ export const SPECSTREAM_BASE_URL = "https://api.specstream.ai";
3
+ export const MAX_EXTRACT_URLS = 20;
4
+ class BaseSpecStreamAPIWrapper {
5
+ specstreamApiKey;
6
+ apiBaseUrl;
7
+ constructor(params = {}) {
8
+ const apiKey = (params.specstreamApiKey ?? getEnvironmentVariable("SPECSTREAM_API_KEY") ?? "").trim();
9
+ if (!apiKey) {
10
+ throw new Error("No SpecStream API key found. Pass specstreamApiKey or set SPECSTREAM_API_KEY. Create a key under Agents & API in your SpecStream workspace.");
11
+ }
12
+ this.specstreamApiKey = apiKey.replace(/^bearer\s+/i, "");
13
+ this.apiBaseUrl = (params.apiBaseUrl ?? getEnvironmentVariable("SPECSTREAM_BASE_URL") ?? SPECSTREAM_BASE_URL).replace(/\/+$/, "");
14
+ }
15
+ async call(method, path, body) {
16
+ const response = await fetch(`${this.apiBaseUrl}${path}`, {
17
+ method,
18
+ headers: {
19
+ Authorization: `Bearer ${this.specstreamApiKey}`,
20
+ Accept: "application/json",
21
+ ...(body === undefined ? {} : { "Content-Type": "application/json" }),
22
+ "User-Agent": "@specstream/langchain",
23
+ },
24
+ body: body === undefined ? undefined : JSON.stringify(body),
25
+ });
26
+ let payload = {};
27
+ try {
28
+ payload = await response.json();
29
+ }
30
+ catch {
31
+ // A reply that is not JSON is reported by its status below.
32
+ }
33
+ if (!response.ok || !payload?.data) {
34
+ throw new Error(`Error ${response.status}: ${payload?.error?.message ?? "Unknown error"}`);
35
+ }
36
+ return payload.data;
37
+ }
38
+ }
39
+ /**
40
+ * A wrapper that encapsulates access to the SpecStream Search API
41
+ * (`GET /v1/web-search`). Primarily used for testing.
42
+ */
43
+ export class SpecStreamSearchAPIWrapper extends BaseSpecStreamAPIWrapper {
44
+ async rawResults(params) {
45
+ const query = new URLSearchParams({ q: params.q.trim() });
46
+ if (params.passages) {
47
+ query.set("passages", "true");
48
+ }
49
+ return this.call("GET", `/v1/web-search?${query.toString()}`);
50
+ }
51
+ }
52
+ /**
53
+ * A wrapper that encapsulates access to the SpecStream Extract API
54
+ * (`POST /v1/extract`). Primarily used for testing.
55
+ */
56
+ export class SpecStreamExtractAPIWrapper extends BaseSpecStreamAPIWrapper {
57
+ async rawResults(params) {
58
+ const urls = params.urls.map((url) => url.trim()).filter(Boolean).slice(0, MAX_EXTRACT_URLS);
59
+ return this.call("POST", "/v1/extract", {
60
+ urls,
61
+ format: params.format === "text" ? "text" : "markdown",
62
+ });
63
+ }
64
+ }
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@specstream/langchain",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "The live web data engine for LangChain.js: Search API ($5 per 1,000 searches), Extract API ($0.80 per 1,000 pages) and Deep Search.",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "main": "./dist/cjs/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "require": {
18
+ "types": "./dist/cjs/index.d.ts",
19
+ "default": "./dist/cjs/index.js"
20
+ }
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "sideEffects": false,
30
+ "scripts": {
31
+ "build": "rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json','{\\\"type\\\":\\\"commonjs\\\"}\\n')\"",
32
+ "typecheck": "tsc -p tsconfig.test.json",
33
+ "test": "vitest run",
34
+ "prepack": "npm run build"
35
+ },
36
+ "dependencies": {
37
+ "zod": "^3.25.76 || ^4"
38
+ },
39
+ "peerDependencies": {
40
+ "@langchain/core": "^1.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@langchain/core": "^1.0.0",
44
+ "@types/node": "^22.0.0",
45
+ "typescript": "5.9.3",
46
+ "vitest": "^3.2.4"
47
+ },
48
+ "keywords": [
49
+ "langchain",
50
+ "langchainjs",
51
+ "specstream",
52
+ "search-api",
53
+ "extract-api",
54
+ "web-search",
55
+ "web-data",
56
+ "live-web",
57
+ "agents"
58
+ ],
59
+ "author": {
60
+ "name": "SpecStream",
61
+ "email": "support@specstream.ai",
62
+ "url": "https://specstream.ai"
63
+ },
64
+ "license": "MIT",
65
+ "homepage": "https://specstream.ai/docs/langchain",
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "git+https://github.com/specstream/specstream-tools.git",
69
+ "directory": "packages/langchain-js"
70
+ },
71
+ "bugs": {
72
+ "url": "https://github.com/specstream/specstream-tools/issues"
73
+ },
74
+ "publishConfig": {
75
+ "access": "public"
76
+ }
77
+ }