LangGraph Tools
@xbbg/xnews-langgraph (0.2.3) wraps xnews for agents: provider-agnostic LangChain tools, finite LangGraph nodes, and a starter analyst. It does not install or choose a model provider.
npm install @xbbg/xnews-langgraph @xbbg/xnews @langchain/core @langchain/langgraph langchain zodPeer ranges: @xbbg/xnews ^0.2.2, @langchain/core ^1.2.9, @langchain/langgraph ^1.4.12, langchain ^1.5.10, zod ^3.25.76 || ^4.2.0. Node.js 24 or newer, ESM only.
Tools
createXnewsTools() always returns the same ten tools. There is no approval middleware, privileged tier, or hidden exclusion:
| Tool | Operations | Covers |
|---|---|---|
xnews_news | company, topic, watchlist | News feeds |
xnews_research | search | Research-paper sources |
xnews_data | fetch | Releases from host-bound DataSources |
xnews_events | snapshot, across | Active-event snapshots from host-bound EventSources |
xnews_works | search, across, resolve_identity | Catalog search and identity resolution |
xnews_files | resolve, download_file | File candidates and downloads, limited to host-approved records and files |
xnews_extract | text, pdf_text, pdf_images, djvu, xlsx, zip, csv_records, csv_table | Decoding host-bound bytes |
xnews_ocr | images, pdf | OCR through the host-configured endpoint |
xnews_transcribe | youtube_captions, pcm | Captions and transcription through host-bound ASR backends |
xnews_catalog | capabilities, providers, datasets, request_data_urls, request_event_urls, request_works_urls | Network-free capability and endpoint lookups |
Each tool takes an operation discriminator. Data sources, event sources, catalogs, work records, files, binary artifacts, and ASR backends are referenced by opaque keys that the host binds in the runtime context; credentials and transport settings are never model arguments. xnews_files only accepts host-bound workRecords and workFiles keys: resolve returns candidates for host review, and the host must validate an origin and bind the approved WorkFile before the model can call download_file.
Model-facing schemas are projected into one closed object per tool (no root-level unions, inlined references, no bounded-repetition keywords), the one shape OpenAI, Anthropic, and Gemini all accept. The Zod schemas remain the contract and are enforced when the tool runs; a violating call returns an actionable error the agent can retry. projectToolSchema is exported for hosts that bind their own tools.
Runtime context
The host passes an XnewsRuntimeContext with every call. This example binds a custom data source and calls the data tool directly, without a model:
import type { DataSource } from "@xbbg/xnews";
import { createXnewsTools, type XnewsRuntimeContext } from "@xbbg/xnews-langgraph";
interface CurvePoint {
readonly tenor: string;
readonly rate: number;
}
const curve: DataSource<CurvePoint> = {
provider: "example-curve",
dataset: "par-curve",
requestUrls: () => ["https://data.example.com/curve.json"],
fetchRelease: async () => ({
provider: "example-curve",
dataset: "par-curve",
asOf: "2026-11-02",
url: "https://data.example.com/curve.json",
rows: [
{ tenor: "2Y", rate: 3.61 },
{ tenor: "10Y", rate: 4.12 },
],
}),
};
const context = {
timeoutMs: 20_000,
maxResponseBytes: 16 * 1024 * 1024,
secUserAgent: process.env.SEC_USER_AGENT,
msrbAcceptTermsOfUse: process.env.EMMA_TERMS_ACCEPTED === "true",
credentials: { openAlexApiKey: process.env.OPENALEX_API_KEY },
dataSources: { curve },
artifactByteCap: 8 * 1024 * 1024,
} satisfies XnewsRuntimeContext;
const dataTool = createXnewsTools().find((tool) => tool.name === "xnews_data");
const message = await dataTool?.invoke(
{ type: "tool_call", id: "call-1", name: "xnews_data", args: { operation: "fetch", source: "curve" } },
{ context },
);
console.log(message?.content); // bounded JSON digest for the modelContext fields: fetch, signal, timeoutMs, maxResponseBytes, redirect, allowCrossOriginRedirects, userAgent, secUserAgent, msrbAcceptTermsOfUse, credentials (openAlexApiKey, annasArchiveKey, byProvider, privateValues), mirrors, dataSources, eventSources, worksSources, workRecords, workFiles, binaryArtifacts, realtimeAsrBackends, ocr, digestCaps, and artifactByteCap. Declaring the object with satisfies XnewsRuntimeContext keeps it assignable to LangGraph's context option.
Results and privacy
Every tool returns a ToolMessage with two payloads:
content: a deterministic, bounded digest for the model, with status, counts, selected records, warnings, source references, and explicit omission counts.artifact: host-only structured data. Envelopes stay complete except that operator secrets are redacted, and binary fields obeyartifactByteCap; over-cap bytes are replaced by their size, SHA-256 hash, and a truncation record.
Redaction targets the operator's data: every value the host supplied (API keys, the SEC contact identity, mirror origins, OCR endpoints and keys, credentials.privateValues) becomes [REDACTED] wherever it appears, as do credential-named fields, Bearer tokens, URL userinfo, and credential-named query parameters. Public content such as article paths and a filing's printed contact details is left intact. Artifacts live in message state and reach checkpointers and LangChain callbacks/tracers, so protect those stores or disable external tracing; the package configures no tracing backend.
Analyst
createXnewsAnalyst builds a LangChain createAgent graph over any tool-capable chat model and returns the native graph (invoke, stream, streamEvents):
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
import { collectXnewsEvidence, createXnewsAnalyst, resolveXnewsCitation } from "@xbbg/xnews-langgraph";
declare const model: BaseChatModel; // construct it with your chosen provider package
const analyst = createXnewsAnalyst({ model });
const result = await analyst.invoke(
{ messages: [{ role: "user", content: "What changed in semiconductor supply risk this week?" }] },
{ configurable: { thread_id: "supply-risk" }, context: { secUserAgent: "my-app/1.0 ops@example.com" } },
);
console.log(result.structuredResponse?.summary);
const evidence = collectXnewsEvidence(result.messages);
for (const source of result.structuredResponse?.sources ?? []) {
const record = resolveXnewsCitation(source.id, evidence);
console.log(record?.url ?? "unresolved citation", record?.publishedAt);
}- By default the analyst binds all ten tools and parses
structuredResponsewithXnewsAnalystResultSchema:summary,claims(statement, evidence, confidence),sources,uncertainty,limitations,providerDiagnostics, andgeneratedAt.tools,systemPrompt,contextSchema,resultSchema, andcheckpointercan be supplied. generatedAtis stamped by the runtime when the result is parsed, and every model call carries the current UTC date with an instruction to judge tool dates against it, because models otherwise date their output to their training era.- Citations. Digest items with a stable identifier carry a 24-character
ref.collectXnewsEvidenceindexes what the tools reported byref, full id, and theprovider|guidprefix models tend to truncate to;resolveXnewsCitationreturns the recorded url, title, provider, and date, orundefined, so a fabricated citation stays visible.
Finite watcher nodes
Infinite async generators are not tools. These factories perform exactly one poll or drain step and return serializable checkpoint updates:
| Factory | State it maintains |
|---|---|
createXnewsNewsNode | seenIds (bounded) and addedItems, plus the full result |
createXnewsDataWatchNode | sinceAsOf / sinceSequence, the release, and a repeated-failure count |
createXnewsEventWatchNode | Bounded seenIds, the authoritative result.events, and the addedEvents delta |
createXnewsRealtimeAsrNode | A queue of base64 PCM chunks, consumed chunk ids, and emitted events |
import { Annotation, END, MemorySaver, START, StateGraph } from "@langchain/langgraph";
import type { NewsFeedResult, NewsItem } from "@xbbg/xnews";
import { createXnewsNewsNode, type XnewsRuntimeContext } from "@xbbg/xnews-langgraph";
const NewsState = Annotation.Root({
seenIds: Annotation<readonly string[] | undefined>(),
addedItems: Annotation<readonly NewsItem[] | undefined>(),
result: Annotation<NewsFeedResult | undefined>(),
});
const graph = new StateGraph(NewsState)
.addNode("poll", createXnewsNewsNode({ subject: { kind: "topic", query: "rates" }, sources: ["google-news"] }))
.addEdge(START, "poll")
.addEdge("poll", END)
.compile({ checkpointer: new MemorySaver() });
const context = { timeoutMs: 20_000 } satisfies XnewsRuntimeContext;
const config = { configurable: { thread_id: "rates" }, context };
const first = await graph.invoke({}, config); // everything in the feed is new
const second = await graph.invoke({}, config); // only items not seen in the checkpoint
console.log(first.addedItems?.length, second.addedItems?.length);A checkpoint is committed after a successful step, so rerunning committed state does not replay consumed ids, dates, sequences, or chunks. Realtime ASR opens a new backend session per non-empty invocation and never puts backend objects in state; maxEventsPerStep defaults to 4,000 (hard cap 10,000) and context.timeoutMs bounds each invocation (30 seconds by default, at most two minutes).
Host responsibilities
The host, never the model, owns credentials and provider accounts, the SEC contact identity, EMMA terms acceptance, transport settings (fetch, signals, timeouts, response ceilings, redirects, user agent), mirror origins, OCR and transcription endpoints, request volume and cost, artifact and digest caps, and checkpointer security and retention. The capability registries are exported for inspection: XNEWS_CAPABILITY_REGISTRY, XNEWS_OPERATION_REGISTRY, XNEWS_NEWS_PROVIDERS, XNEWS_RESEARCH_PROVIDERS, XNEWS_EVENT_PROVIDERS, XNEWS_WORKS_PROVIDERS, and XNEWS_DATASETS.
Related
- Watchlists & Watchers: the generator-based watchers these nodes replace in graphs.
- Transport & Security: what the context's transport fields do.
- Changelog: release notes for both packages.
