Skip to content

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.

bash
npm install @xbbg/xnews-langgraph @xbbg/xnews @langchain/core @langchain/langgraph langchain zod

Peer 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:

ToolOperationsCovers
xnews_newscompany, topic, watchlistNews feeds
xnews_researchsearchResearch-paper sources
xnews_datafetchReleases from host-bound DataSources
xnews_eventssnapshot, acrossActive-event snapshots from host-bound EventSources
xnews_workssearch, across, resolve_identityCatalog search and identity resolution
xnews_filesresolve, download_fileFile candidates and downloads, limited to host-approved records and files
xnews_extracttext, pdf_text, pdf_images, djvu, xlsx, zip, csv_records, csv_tableDecoding host-bound bytes
xnews_ocrimages, pdfOCR through the host-configured endpoint
xnews_transcribeyoutube_captions, pcmCaptions and transcription through host-bound ASR backends
xnews_catalogcapabilities, providers, datasets, request_data_urls, request_event_urls, request_works_urlsNetwork-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:

ts
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 model

Context 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 obey artifactByteCap; 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):

ts
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 structuredResponse with XnewsAnalystResultSchema: summary, claims (statement, evidence, confidence), sources, uncertainty, limitations, providerDiagnostics, and generatedAt. tools, systemPrompt, contextSchema, resultSchema, and checkpointer can be supplied.
  • generatedAt is 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. collectXnewsEvidence indexes what the tools reported by ref, full id, and the provider|guid prefix models tend to truncate to; resolveXnewsCitation returns the recorded url, title, provider, and date, or undefined, 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:

FactoryState it maintains
createXnewsNewsNodeseenIds (bounded) and addedItems, plus the full result
createXnewsDataWatchNodesinceAsOf / sinceSequence, the release, and a repeated-failure count
createXnewsEventWatchNodeBounded seenIds, the authoritative result.events, and the addedEvents delta
createXnewsRealtimeAsrNodeA queue of base64 PCM chunks, consumed chunk ids, and emitted events
ts
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.

xbbg is independent open-source software and is not affiliated with, endorsed by, sponsored by, or approved by Bloomberg Finance L.P.