@telemai/sdk 0.3.2 → 0.4.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/README.md CHANGED
@@ -167,6 +167,13 @@ const page = await telem.fetch("https://example.com/article", {
167
167
  const { status, content } = page.results[0]
168
168
  ```
169
169
 
170
+ `providers` pins the fetch providers, in order; `tier` picks the fetch tier; and
171
+ `noCache: true` reads the page live instead of from the shared cache. If you leave
172
+ one out, the client uses its environment variable: `TELEM_FETCH_PROVIDERS` (comma
173
+ separated), `TELEM_FETCH_TIER` (lowercased) or `TELEM_FETCH_NO_CACHE` (`1` turns it
174
+ on). The client reads them when you create it. An option you pass wins, so
175
+ `noCache: false` turns off `TELEM_FETCH_NO_CACHE=1` for that call.
176
+
170
177
  A URL that failed still returns a result. Check `result.status` and `result.error`.
171
178
  Do not expect a shorter list. The options are on the
172
179
  [fetch parameters](https://docs.telem.ai/resources/fetch-parameters/) page.
@@ -206,10 +213,22 @@ your last poll. `poll` returns a `SearchResponse`:
206
213
  ```ts
207
214
  const monitor = await telem.monitors.create("Nvidia files a new 8-K with the SEC", { frequency: "1h", ttl: "7d" })
208
215
  const response = await telem.monitors.poll(monitor.id)
216
+ for await (const change of telem.monitors.iterateChanges(monitor.id)) {
217
+ console.log(change.seq, change.kind, change.subject.name)
218
+ }
209
219
  await telem.monitors.update(monitor.id, { status: "paused" })
210
220
  await telem.monitors.delete(monitor.id)
211
221
  ```
212
222
 
223
+ A monitor has an agent unless you create it with `agent: false`. The agent checks every
224
+ result against the query and keeps the ones that match. `iterateChanges` reads what it
225
+ kept, oldest first, until you are caught up, and then stops. Keep the `seq` of the last
226
+ change you handled and pass it as `cursor` next time: no change comes twice, and none
227
+ is skipped. `changes` returns one page (`nextCursor`, `watermark`) and takes
228
+ `cursor`, `since`, `kinds` and `limit`. `agent` returns the agent itself: its status,
229
+ query, frequency and plan version. `monitor.agentState` shows it on the monitor. Read
230
+ `iterateChanges` with `for await`.
231
+
213
232
  `telem.monitors` also has `get` and `list`. See [Monitors](https://docs.telem.ai/resources/monitors/)
214
233
  for the fields and the errors.
215
234
 
@@ -80,8 +80,10 @@ function defaultMessage(status) {
80
80
  if (status === 404) return "Not found";
81
81
  return `Unexpected status code ${status}`;
82
82
  }
83
+ var BLOCKED_MESSAGE = "The request was blocked by a firewall before it reached Telem; the text may have characters that are not allowed.";
83
84
  function statusError(status, body) {
84
- return new TelemError(detailFromBody(body) ?? defaultMessage(status), {
85
+ const fallback = status === 403 && !isObject(body) ? BLOCKED_MESSAGE : defaultMessage(status);
86
+ return new TelemError(detailFromBody(body) ?? fallback, {
85
87
  status,
86
88
  code: codeFromBody(body),
87
89
  details: body
package/dist/index.cjs CHANGED
@@ -60,7 +60,7 @@ var TimeoutError = class extends TelemError {
60
60
  };
61
61
 
62
62
  // src/version.ts
63
- var VERSION = "0.3.2";
63
+ var VERSION = "0.4.0";
64
64
 
65
65
  // src/config.ts
66
66
  var DEFAULT_BASE_URL = "https://router.telem.ai";
@@ -120,6 +120,7 @@ function resolveConfig(options) {
120
120
  if (typeof send !== "function") {
121
121
  throw invalidOption("no fetch on this runtime: pass one with the `fetch` option");
122
122
  }
123
+ if (typeof opts.apiKey === "string" && !opts.apiKey.trim()) throw invalidOption("apiKey is empty");
123
124
  let apiKey = opts.apiKey || env.TELEM_API_KEY || void 0;
124
125
  let baseUrl = opts.baseUrl || env.TELEM_BASE_URL || void 0;
125
126
  if (apiKey === void 0 || baseUrl === void 0) {
@@ -158,6 +159,14 @@ function resolveConfig(options) {
158
159
  autoRouting: opts.autoRouting ?? (trimBlank(env.TELEM_AUTO_ROUTING ?? "") || void 0),
159
160
  maxRoutingProviders: opts.maxRoutingProviders ?? intEnv(env.TELEM_MAX_ROUTING_PROVIDERS),
160
161
  topic: opts.topic ?? (trimBlank(env.TELEM_TOPIC ?? "") || void 0)
162
+ },
163
+ // Fetch defaults: env var only, parsed as the shared option table parses it (CSV,
164
+ // a trimmed name, a "1"-only flag); the tier is lowercased, as the agent plugins
165
+ // send it. No constructor option, and no config file, as above.
166
+ fetchDefaults: {
167
+ providers: csvEnv(env.TELEM_FETCH_PROVIDERS),
168
+ tier: trimBlank(env.TELEM_FETCH_TIER ?? "").toLowerCase() || void 0,
169
+ noCache: env.TELEM_FETCH_NO_CACHE === "1" || void 0
161
170
  }
162
171
  };
163
172
  }
@@ -224,8 +233,10 @@ function defaultMessage(status) {
224
233
  if (status === 404) return "Not found";
225
234
  return `Unexpected status code ${status}`;
226
235
  }
236
+ var BLOCKED_MESSAGE = "The request was blocked by a firewall before it reached Telem; the text may have characters that are not allowed.";
227
237
  function statusError(status, body) {
228
- return new TelemError(detailFromBody(body) ?? defaultMessage(status), {
238
+ const fallback = status === 403 && !isObject(body) ? BLOCKED_MESSAGE : defaultMessage(status);
239
+ return new TelemError(detailFromBody(body) ?? fallback, {
229
240
  status,
230
241
  code: codeFromBody(body),
231
242
  details: body
@@ -492,7 +503,7 @@ function wireMetadata(options, kind) {
492
503
  }
493
504
 
494
505
  // src/methods/fetch.ts
495
- function fetchBody(url, options) {
506
+ function fetchBody(url, options, defaults) {
496
507
  const urls = stringList(
497
508
  typeof url === "string" ? [url] : url,
498
509
  "fetch() requires at least one URL",
@@ -500,11 +511,16 @@ function fetchBody(url, options) {
500
511
  );
501
512
  const { metadata, sessionId } = wireMetadata(options, "fetch");
502
513
  const body = { urls, metadata };
514
+ const providers2 = options.providers ?? defaults.providers;
515
+ const tier = options.tier ?? defaults.tier;
516
+ const noCache = options.noCache ?? defaults.noCache;
503
517
  const block = {};
504
- if (options.providers !== void 0) block.providers = options.providers;
518
+ if (providers2 !== void 0) block.providers = providers2;
505
519
  if (options.inlineContent !== void 0) block.inline_content = options.inlineContent;
506
520
  if (options.inlineMaxChars !== void 0) block.inline_max_chars = options.inlineMaxChars;
507
521
  if (options.contentFormat !== void 0) block.content_format = options.contentFormat;
522
+ if (tier !== void 0) block.tier = tier;
523
+ if (noCache !== void 0) block.no_cache = noCache;
508
524
  if (Object.keys(block).length) body.options = block;
509
525
  if (sessionId !== void 0) body.session_id = sessionId;
510
526
  return body;
@@ -545,12 +561,17 @@ function buildFetchResponse(interaction) {
545
561
  };
546
562
  }
547
563
  async function fetchUrls(config, url, options) {
548
- const body = fetchBody(url, options);
564
+ const body = fetchBody(url, options, config.fetchDefaults);
549
565
  return buildFetchResponse(await request(config, "POST", "/v1/fetch", body, options.signal));
550
566
  }
551
567
 
552
568
  // src/methods/search.ts
553
569
  var MIN_SCHEMA_VERSION = 2;
570
+ function providersRefusal(include, includeFromCall, excludeFromCall) {
571
+ const where = (half, env, fromCall) => fromCall ? `the ${half} search() option` : `the client's ${half} or ${env}`;
572
+ const names = include.join(", ");
573
+ return `providersExclude removes every provider in providersInclude (${names}), so no provider is left and no search was sent. Remove ${names} from providersInclude (set by ${where("providersInclude", "TELEM_PROVIDERS_INCLUDE", includeFromCall)}) or from providersExclude (set by ${where("providersExclude", "TELEM_PROVIDERS_EXCLUDE", excludeFromCall)}).`;
574
+ }
554
575
  function searchBlock(defaults, options) {
555
576
  const fromCallTier = options.tier !== void 0;
556
577
  const fromCallFields = options.fields !== void 0;
@@ -564,7 +585,13 @@ function searchBlock(defaults, options) {
564
585
  let exclude = options.providersExclude ?? defaults.providersExclude;
565
586
  if (include !== void 0 && exclude !== void 0) {
566
587
  const excluded = new Set(exclude);
567
- include = include.filter((name) => !excluded.has(name));
588
+ const kept = include.filter((name) => !excluded.has(name));
589
+ if (include.length && !kept.length) {
590
+ throw invalidRequest(
591
+ providersRefusal(include, options.providersInclude !== void 0, options.providersExclude !== void 0)
592
+ );
593
+ }
594
+ include = kept;
568
595
  exclude = void 0;
569
596
  }
570
597
  const includeFullContent = options.includeFullContent ?? defaults.includeFullContent;
@@ -674,7 +701,8 @@ function providerRun(run) {
674
701
  related: envelope.related ?? null,
675
702
  verticals: envelope.verticals ?? null,
676
703
  usage: envelope.usage ?? null,
677
- warnings: asArray(envelope.warnings)
704
+ warnings: asArray(envelope.warnings),
705
+ sameStory: isObject(envelope.same_story) ? envelope.same_story : null
678
706
  };
679
707
  }
680
708
  function buildSearchResponse(interaction) {
@@ -698,9 +726,25 @@ async function search(config, query, options) {
698
726
  }
699
727
 
700
728
  // src/methods/monitors.ts
729
+ function agentStateFrom(data) {
730
+ return {
731
+ engine: String(data.engine ?? ""),
732
+ status: String(data.status ?? ""),
733
+ needsAttention: data.needs_attention === true,
734
+ planVersion: typeof data.plan_version === "number" ? data.plan_version : null,
735
+ nextRunAt: typeof data.next_run_at === "string" ? data.next_run_at : null,
736
+ watermark: Number(data.watermark ?? 0),
737
+ // No compile job yet is null, not an object of empty strings.
738
+ compile: isObject(data.compile) ? {
739
+ jobId: String(data.compile.job_id ?? ""),
740
+ status: String(data.compile.status ?? ""),
741
+ error: typeof data.compile.error === "string" ? data.compile.error : null
742
+ } : null
743
+ };
744
+ }
701
745
  function monitorFrom(body) {
702
746
  const data = asObject(body);
703
- return {
747
+ const monitor = {
704
748
  id: String(data.id ?? ""),
705
749
  query: data.query ?? "",
706
750
  frequency: data.frequency ?? "",
@@ -713,12 +757,85 @@ function monitorFrom(body) {
713
757
  webhookSecret: data.webhook_secret ?? null,
714
758
  raw: data
715
759
  };
760
+ if (data.agent_state !== void 0) {
761
+ monitor.agentState = isObject(data.agent_state) ? agentStateFrom(data.agent_state) : null;
762
+ }
763
+ return monitor;
764
+ }
765
+ function agentDocumentFrom(body) {
766
+ const data = asObject(body);
767
+ const document = { raw: data };
768
+ if (typeof data.status === "string") document.status = data.status;
769
+ if (typeof data.query === "string") document.query = data.query;
770
+ if (typeof data.frequency === "string") document.frequency = data.frequency;
771
+ if (typeof data.spec_version === "number") document.specVersion = data.spec_version;
772
+ if (data.budget !== void 0) document.budget = isObject(data.budget) ? data.budget : null;
773
+ if (typeof data.spend_usd === "number") document.spendUsd = data.spend_usd;
774
+ return document;
775
+ }
776
+ function changeRecordFrom(entry) {
777
+ const subject = asObject(entry.subject);
778
+ return {
779
+ seq: Number(entry.seq ?? 0),
780
+ type: String(entry.type ?? ""),
781
+ kind: String(entry.kind ?? ""),
782
+ monitorId: String(entry.monitor_id ?? ""),
783
+ subject: {
784
+ entityId: typeof subject.entity_id === "string" ? subject.entity_id : null,
785
+ name: typeof subject.name === "string" ? subject.name : null
786
+ },
787
+ // The values depend on the kind (an object, a status string, nothing): as sent.
788
+ before: entry.before ?? null,
789
+ after: entry.after ?? null,
790
+ factIds: asArray(entry.fact_ids).map(String),
791
+ at: String(entry.at ?? ""),
792
+ raw: entry
793
+ };
794
+ }
795
+ function changesPageFrom(body) {
796
+ const data = asObject(body);
797
+ return {
798
+ changes: asArray(data.changes).filter(isObject).map(changeRecordFrom),
799
+ nextCursor: Number(data.next_cursor ?? 0),
800
+ watermark: Number(data.watermark ?? 0)
801
+ };
802
+ }
803
+ function listQuery(options) {
804
+ const params = new URLSearchParams();
805
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
806
+ if (options.cursor !== void 0) params.set("cursor", options.cursor);
807
+ const query = params.toString();
808
+ return query ? `?${query}` : "";
809
+ }
810
+ function sinceParam(since) {
811
+ if (typeof since === "string") return since;
812
+ if (Number.isNaN(since.getTime())) throw invalidRequest("since is not a valid date");
813
+ return since.toISOString();
814
+ }
815
+ function changesQuery(options) {
816
+ const params = new URLSearchParams();
817
+ if (options.cursor !== void 0) params.set("cursor", String(options.cursor));
818
+ if (options.since !== void 0) params.set("since", sinceParam(options.since));
819
+ if (options.kinds !== void 0) params.set("kinds", options.kinds.join(","));
820
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
821
+ const query = params.toString();
822
+ return query ? `?${query}` : "";
716
823
  }
717
824
  function createMonitors(config) {
718
825
  const path = (monitorId, suffix = "") => {
719
826
  if (!monitorId || /^\.+$/.test(monitorId)) throw invalidRequest("a monitor id is required");
720
827
  return `/v1/monitors/${encodeURIComponent(monitorId)}${suffix}`;
721
828
  };
829
+ const listPage = async (options = {}) => {
830
+ const route = `/v1/monitors${listQuery(options)}`;
831
+ const data = asObject(await request(config, "GET", route, void 0, options.signal));
832
+ const nextCursor = typeof data.next_cursor === "string" ? data.next_cursor : null;
833
+ return { monitors: asArray(data.monitors).map(monitorFrom), nextCursor };
834
+ };
835
+ const changes = async (monitorId, options = {}) => {
836
+ const route = `${path(monitorId, "/agent/changes")}${changesQuery(options)}`;
837
+ return changesPageFrom(await request(config, "GET", route, void 0, options.signal));
838
+ };
722
839
  return {
723
840
  /** Create a monitor. Telem runs `query` on the schedule. */
724
841
  async create(query, options = {}) {
@@ -732,15 +849,25 @@ function createMonitors(config) {
732
849
  if (options.metadata !== void 0) body.metadata = options.metadata;
733
850
  if (options.webhookUrl !== void 0) body.webhook_url = options.webhookUrl;
734
851
  if (options.ttl !== void 0) body.ttl = options.ttl;
735
- const data = await request(config, "POST", "/v1/monitors", body, options.signal, "rate_limit");
852
+ if (options.agent !== void 0) body.agent = options.agent;
853
+ const data = await request(config, "POST", "/v1/monitors", body, options.signal, "rate_limit").catch(
854
+ (error) => {
855
+ if (error instanceof TimeoutError) {
856
+ throw new TimeoutError(
857
+ `${error.message}. The monitor may exist: list monitors before you create it again.`,
858
+ error.cause
859
+ );
860
+ }
861
+ throw error;
862
+ }
863
+ );
736
864
  return monitorFrom(data);
737
865
  },
738
- /** List your monitors, newest first (server default 50, maximum 200). */
866
+ /** List your monitors, newest first. `listPage` also gives the next cursor. */
739
867
  async list(options = {}) {
740
- const query = options.limit === void 0 ? "" : `?limit=${options.limit}`;
741
- const body = await request(config, "GET", `/v1/monitors${query}`, void 0, options.signal);
742
- return asArray(asObject(body).monitors).map(monitorFrom);
868
+ return (await listPage(options)).monitors;
743
869
  },
870
+ listPage,
744
871
  /** Get one monitor. */
745
872
  async get(monitorId, options = {}) {
746
873
  return monitorFrom(await request(config, "GET", path(monitorId), void 0, options.signal));
@@ -750,7 +877,7 @@ function createMonitors(config) {
750
877
  const body = {};
751
878
  if (options.status !== void 0) body.status = options.status;
752
879
  if (options.frequency !== void 0) body.frequency = options.frequency;
753
- if (options.webhookUrl !== void 0) body.webhook_url = options.webhookUrl;
880
+ if (options.webhookUrl !== void 0) body.webhook_url = options.webhookUrl || null;
754
881
  const route = path(monitorId);
755
882
  return monitorFrom(await request(config, "PATCH", route, body, options.signal));
756
883
  },
@@ -766,6 +893,23 @@ function createMonitors(config) {
766
893
  /** Delete a monitor. A repeat deletes what a failed delete left. */
767
894
  async delete(monitorId, options = {}) {
768
895
  await request(config, "DELETE", path(monitorId), void 0, options.signal);
896
+ },
897
+ /** Read the monitor agent's document. A monitor without an agent answers 404. */
898
+ async agent(monitorId, options = {}) {
899
+ const route = path(monitorId, "/agent");
900
+ return agentDocumentFrom(await request(config, "GET", route, void 0, options.signal));
901
+ },
902
+ /** One page of the agent's changes. Keep `nextCursor` and pass it back as `cursor`. */
903
+ changes,
904
+ /** Every change after `cursor`, page by page, until you are caught up. */
905
+ async *iterateChanges(monitorId, options = {}) {
906
+ let cursor = options.cursor ?? 0;
907
+ for (; ; ) {
908
+ const page = await changes(monitorId, { cursor, signal: options.signal });
909
+ for (const change of page.changes) yield change;
910
+ if (!page.changes.length || page.nextCursor <= cursor || page.nextCursor >= page.watermark) return;
911
+ cursor = page.nextCursor;
912
+ }
769
913
  }
770
914
  };
771
915
  }
@@ -798,7 +942,8 @@ async function providers(config) {
798
942
  method: entry.method ?? null,
799
943
  url: entry.url ?? null,
800
944
  normalized: entry.normalized ?? false,
801
- tiers: asArray(entry.tiers)
945
+ tiers: asArray(entry.tiers),
946
+ autoRouted: entry.auto_routed ?? null
802
947
  }));
803
948
  }
804
949
 
@@ -867,7 +1012,8 @@ var Telem = class {
867
1012
  maxRetries;
868
1013
  /** The session read surface: `list` and `results(id)`. */
869
1014
  sessions;
870
- /** Monitors: `create`, `list`, `get`, `update`, `poll`, `delete`. */
1015
+ /** Monitors: `create`, `list`, `listPage`, `get`, `update`, `poll`, `delete`, and the
1016
+ * agent reads `agent`, `changes` and `iterateChanges`. */
871
1017
  monitors;
872
1018
  #config;
873
1019
  /**
package/dist/index.d.cts CHANGED
@@ -96,10 +96,17 @@ type SearchOptions = {
96
96
  * `numResults`/`includeRaw`/`rerank`/`providerOverrides` are per-call intent only. */
97
97
  type SearchDefaults = Pick<SearchOptions, "tier" | "fields" | "providersInclude" | "providersExclude" | "includeFullContent" | "autoRouting" | "maxRoutingProviders" | "topic">;
98
98
  type FetchOptions = {
99
+ /** Fetch provider chain, in order. Defaults to `TELEM_FETCH_PROVIDERS`. */
99
100
  providers?: string[];
100
101
  inlineContent?: boolean;
101
102
  inlineMaxChars?: number;
102
103
  contentFormat?: string;
104
+ /** Fetch tier: `minimalist`, `default`, `extended` or `max`. Sent as given; the
105
+ * server rejects an unknown one. Defaults to `TELEM_FETCH_TIER`, lowercased. */
106
+ tier?: string;
107
+ /** `true` reads every page live and skips the shared fetch cache. Defaults to
108
+ * `TELEM_FETCH_NO_CACHE` (on with exactly `"1"`); `false` overrides it. */
109
+ noCache?: boolean;
103
110
  session?: string;
104
111
  /** Abort this call: the request stops, no retry follows, and the call rejects
105
112
  * with the signal's reason. Merged with the per-attempt timeout. */
@@ -150,6 +157,8 @@ type ProviderRun = {
150
157
  verticals: Json | null;
151
158
  usage: Json | unknown[] | null;
152
159
  warnings: Json[];
160
+ /** Monitor polls: results folded as the same story, `{leadUrl: [{url, title}]}`. */
161
+ sameStory: Record<string, Json[]> | null;
153
162
  raw: Json | null;
154
163
  };
155
164
  type SearchResponse = {
@@ -200,6 +209,8 @@ type ProviderInfo = {
200
209
  url: string | null;
201
210
  normalized: boolean;
202
211
  tiers: string[];
212
+ /** Whether auto-routing can pick it; `null` from a router that does not say. */
213
+ autoRouted: boolean | null;
203
214
  };
204
215
  /** One session as `sessions.list` reports it. */
205
216
  type SessionSummary = {
@@ -243,6 +254,10 @@ type MonitorCreateOptions = {
243
254
  webhookUrl?: string;
244
255
  /** Delete the monitor after this long, such as `"12h"` or `"7d"` (at most `"30d"`). Without it, the monitor never expires. */
245
256
  ttl?: string;
257
+ /** Start the monitor agent with the monitor: it checks every result against the query
258
+ * and keeps the ones that match. Telem turns it on when you leave this out; `false`
259
+ * creates the monitor without one. Read what the agent keeps with `monitors.changes`. */
260
+ agent?: boolean;
246
261
  signal?: AbortSignal;
247
262
  };
248
263
  /** Options for `monitors.update`. Send at least one field. */
@@ -250,7 +265,7 @@ type MonitorUpdateOptions = {
250
265
  /** `"paused"` or `"active"`. */
251
266
  status?: string;
252
267
  frequency?: string;
253
- /** A new webhook URL, with a new `webhookSecret` in the response. `null` removes it. */
268
+ /** A new webhook URL, with a new `webhookSecret` in the response. `null` or `""` removes it. */
254
269
  webhookUrl?: string | null;
255
270
  signal?: AbortSignal;
256
271
  };
@@ -267,25 +282,144 @@ type Monitor = {
267
282
  id: string;
268
283
  query: string;
269
284
  frequency: string;
270
- /** `active` or `paused` (you paused it). */
285
+ /** `active`, `paused` (you paused it) or `ended` (past `expiresAt`). */
271
286
  status: string;
272
287
  sessionId: string;
273
288
  createdAt: string;
274
289
  lastPolledAt: string | null;
275
- /** When Telem deletes the monitor; `null`: never. */
290
+ /** When the monitor ends; `null`: never. */
276
291
  expiresAt: string | null;
277
292
  webhookUrl: string | null;
278
293
  /** Signs the webhook deliveries. Only in the response that set `webhookUrl`. */
279
294
  webhookSecret: string | null;
295
+ /** The monitor agent's state. `null`: the monitor has no agent. Absent when the router
296
+ * sends none, as a router from before the agent reads does. */
297
+ agentState?: AgentState | null;
298
+ raw: Json;
299
+ };
300
+ /** The latest compile of an agent's plan (`AgentState.compile`). */
301
+ type AgentCompileState = {
302
+ jobId: string;
303
+ /** The compile job's status, such as `done` or `failed`. */
304
+ status: string;
305
+ /** Why the compile failed; `null` when it did not. */
306
+ error: string | null;
307
+ };
308
+ /** The state of a monitor's agent: `monitor.agentState`. */
309
+ type AgentState = {
310
+ /** The agent engine, `"v4"` today. */
311
+ engine: string;
312
+ /** Such as `compiling`, `ready`, `paused` or `failed`. */
313
+ status: string;
314
+ /** `true` when `status` is `failed`. */
315
+ needsAttention: boolean;
316
+ /** The version of the agent's plan; `null` before it has one. */
317
+ planVersion: number | null;
318
+ /** When the agent runs next; `null` when no run is scheduled. */
319
+ nextRunAt: string | null;
320
+ /** The `seq` of the newest change `monitors.changes` can serve now. */
321
+ watermark: number;
322
+ /** The latest compile of the plan; `null` when there has been none. */
323
+ compile: AgentCompileState | null;
324
+ };
325
+ /** The monitor agent's document: `monitors.agent`. A field is there only when the
326
+ * router sent it. The rest of the document (the plan, the counts, the runs) is in `raw`. */
327
+ type AgentDocument = {
328
+ /** Such as `compiling`, `ready`, `paused`, `failed` or `ended`. */
329
+ status?: string;
330
+ query?: string;
331
+ frequency?: string;
332
+ /** The version of the plan; 0 while the agent compiles. */
333
+ specVersion?: number;
334
+ /** The agent's spending limits; `null` when none are set. */
335
+ budget?: Json | null;
336
+ /** What the agent has spent, in US dollars. */
337
+ spendUsd?: number;
280
338
  raw: Json;
281
339
  };
340
+ /** The item a change is about. `entityId` is `null` for a change to the whole monitor,
341
+ * such as a run or a plan change. */
342
+ type ChangeSubject = {
343
+ entityId: string | null;
344
+ name: string | null;
345
+ };
346
+ /** One change the monitor agent made. */
347
+ type ChangeRecord = {
348
+ /** The change's place in the stream. It grows with every change and is never reused.
349
+ * Pass the last one you handled as `cursor` to read on from there. */
350
+ seq: number;
351
+ /** The change's own type, such as `new_object`. */
352
+ type: string;
353
+ /** What happened: one of the six kinds `entity_added`, `value_changed`,
354
+ * `fact_status_changed`, `alert`, `run` or `plan_changed`. */
355
+ kind: string;
356
+ monitorId: string;
357
+ subject: ChangeSubject;
358
+ /** What it was before, as the router sent it; `null` when there is no before. Its shape
359
+ * depends on `kind`. */
360
+ before: unknown;
361
+ /** What it is now, as the router sent it. */
362
+ after: unknown;
363
+ /** The facts behind the change; empty when the router names none. */
364
+ factIds: string[];
365
+ /** When it happened, ISO 8601. */
366
+ at: string;
367
+ raw: Json;
368
+ };
369
+ /** One page of `monitors.changes`. */
370
+ type ChangesPage = {
371
+ changes: ChangeRecord[];
372
+ /** Keep this and pass it as `cursor` to read the changes after this page. On a full
373
+ * page (`limit` changes) it is the `seq` of the last change. On a shorter page, an
374
+ * empty one included, it is `watermark`: the read got that far, past the changes
375
+ * that `kinds` left out too. */
376
+ nextCursor: number;
377
+ /** The `seq` of the newest change that can be read now. You are caught up when
378
+ * `nextCursor` equals it, or when a page is empty. */
379
+ watermark: number;
380
+ };
381
+ /** Options for `monitors.changes`. */
382
+ type MonitorChangesOptions = {
383
+ /** Read the changes after this `seq`. If you leave it out, the page starts at the
384
+ * first change. Do not send it with `since`. */
385
+ cursor?: number;
386
+ /** Read from this time on: a `Date`, or an ISO 8601 time with an offset such as
387
+ * `"2026-11-03T12:00:00Z"`. Do not send it with `cursor`. */
388
+ since?: string | Date;
389
+ /** Only these kinds of change: `entity_added`, `value_changed`, `fact_status_changed`,
390
+ * `alert`, `run` or `plan_changed`, for example `["alert", "run"]`. Another name is a
391
+ * 400 (`kinds_invalid`). An empty list is no filter. */
392
+ kinds?: string[];
393
+ /** How many changes to return (server default 200, maximum 500). */
394
+ limit?: number;
395
+ signal?: AbortSignal;
396
+ };
397
+ /** Options for `monitors.iterateChanges`. */
398
+ type MonitorIterateChangesOptions = {
399
+ /** Read the changes after this `seq`. If you leave it out, the walk starts at the
400
+ * first change. */
401
+ cursor?: number;
402
+ signal?: AbortSignal;
403
+ };
404
+ /** Options for `monitors.list` and `monitors.listPage`. */
405
+ type MonitorListOptions = {
406
+ /** How many to return (server default 50, maximum 200). */
407
+ limit?: number;
408
+ /** The `nextCursor` of the page before. */
409
+ cursor?: string;
410
+ signal?: AbortSignal;
411
+ };
412
+ /** One page of `monitors.listPage`. */
413
+ type MonitorPage = {
414
+ monitors: Monitor[];
415
+ /** Pass as `cursor` to get the next page; `null` on the last page. */
416
+ nextCursor: string | null;
417
+ };
282
418
  /** The `telem.monitors` namespace. */
283
419
  type MonitorsResource = {
284
420
  create(query: string, options?: MonitorCreateOptions): Promise<Monitor>;
285
- list(options?: {
286
- limit?: number;
287
- signal?: AbortSignal;
288
- }): Promise<Monitor[]>;
421
+ list(options?: MonitorListOptions): Promise<Monitor[]>;
422
+ listPage(options?: MonitorListOptions): Promise<MonitorPage>;
289
423
  get(monitorId: string, options?: {
290
424
  signal?: AbortSignal;
291
425
  }): Promise<Monitor>;
@@ -294,6 +428,14 @@ type MonitorsResource = {
294
428
  delete(monitorId: string, options?: {
295
429
  signal?: AbortSignal;
296
430
  }): Promise<void>;
431
+ /** Read the monitor agent's document. A monitor without an agent answers 404. */
432
+ agent(monitorId: string, options?: {
433
+ signal?: AbortSignal;
434
+ }): Promise<AgentDocument>;
435
+ /** One page of the agent's changes. Keep `nextCursor` and pass it back as `cursor`. */
436
+ changes(monitorId: string, options?: MonitorChangesOptions): Promise<ChangesPage>;
437
+ /** Every change after `cursor`, page by page, until you are caught up. */
438
+ iterateChanges(monitorId: string, options?: MonitorIterateChangesOptions): AsyncGenerator<ChangeRecord, void, undefined>;
297
439
  };
298
440
 
299
441
  /**
@@ -437,7 +579,8 @@ declare class Telem {
437
579
  readonly maxRetries: number;
438
580
  /** The session read surface: `list` and `results(id)`. */
439
581
  readonly sessions: SessionsResource;
440
- /** Monitors: `create`, `list`, `get`, `update`, `poll`, `delete`. */
582
+ /** Monitors: `create`, `list`, `listPage`, `get`, `update`, `poll`, `delete`, and the
583
+ * agent reads `agent`, `changes` and `iterateChanges`. */
441
584
  readonly monitors: MonitorsResource;
442
585
  /**
443
586
  * @param options - An API key string, or an options object. Anything left unset
@@ -470,4 +613,4 @@ declare class Telem {
470
613
  providers(): Promise<ProviderInfo[]>;
471
614
  }
472
615
 
473
- export { type FetchOptions, type FetchResponse, type FetchResult, type Json, type Monitor, type MonitorCreateOptions, type MonitorPollOptions, type MonitorUpdateOptions, type MonitorsResource, type ProviderInfo, type ProviderRun, RESERVED_METADATA, type SearchDefaults, type SearchOptions, type SearchResponse, type SearchResult, type SessionResults, type SessionSummary, type SessionsResource, type SnapshotArgs, Telem, type TelemClientErrorCode, TelemError, type TelemErrorOptions, type TelemOptions, TimeoutError, type TrajectoryArgs, type TrajectoryEnvelope, type TrajectorySnapshot, buildMetadata, buildSnapshot, contentAnchor, Telem as default, eventNodeKey, fingerprint, sessionKey, snapshotNodeKey, toolMarker, verifyWebhook, windowAnchor, withTrajectory };
616
+ export { type AgentCompileState, type AgentDocument, type AgentState, type ChangeRecord, type ChangeSubject, type ChangesPage, type FetchOptions, type FetchResponse, type FetchResult, type Json, type Monitor, type MonitorChangesOptions, type MonitorCreateOptions, type MonitorIterateChangesOptions, type MonitorListOptions, type MonitorPage, type MonitorPollOptions, type MonitorUpdateOptions, type MonitorsResource, type ProviderInfo, type ProviderRun, RESERVED_METADATA, type SearchDefaults, type SearchOptions, type SearchResponse, type SearchResult, type SessionResults, type SessionSummary, type SessionsResource, type SnapshotArgs, Telem, type TelemClientErrorCode, TelemError, type TelemErrorOptions, type TelemOptions, TimeoutError, type TrajectoryArgs, type TrajectoryEnvelope, type TrajectorySnapshot, buildMetadata, buildSnapshot, contentAnchor, Telem as default, eventNodeKey, fingerprint, sessionKey, snapshotNodeKey, toolMarker, verifyWebhook, windowAnchor, withTrajectory };
package/dist/index.d.ts CHANGED
@@ -96,10 +96,17 @@ type SearchOptions = {
96
96
  * `numResults`/`includeRaw`/`rerank`/`providerOverrides` are per-call intent only. */
97
97
  type SearchDefaults = Pick<SearchOptions, "tier" | "fields" | "providersInclude" | "providersExclude" | "includeFullContent" | "autoRouting" | "maxRoutingProviders" | "topic">;
98
98
  type FetchOptions = {
99
+ /** Fetch provider chain, in order. Defaults to `TELEM_FETCH_PROVIDERS`. */
99
100
  providers?: string[];
100
101
  inlineContent?: boolean;
101
102
  inlineMaxChars?: number;
102
103
  contentFormat?: string;
104
+ /** Fetch tier: `minimalist`, `default`, `extended` or `max`. Sent as given; the
105
+ * server rejects an unknown one. Defaults to `TELEM_FETCH_TIER`, lowercased. */
106
+ tier?: string;
107
+ /** `true` reads every page live and skips the shared fetch cache. Defaults to
108
+ * `TELEM_FETCH_NO_CACHE` (on with exactly `"1"`); `false` overrides it. */
109
+ noCache?: boolean;
103
110
  session?: string;
104
111
  /** Abort this call: the request stops, no retry follows, and the call rejects
105
112
  * with the signal's reason. Merged with the per-attempt timeout. */
@@ -150,6 +157,8 @@ type ProviderRun = {
150
157
  verticals: Json | null;
151
158
  usage: Json | unknown[] | null;
152
159
  warnings: Json[];
160
+ /** Monitor polls: results folded as the same story, `{leadUrl: [{url, title}]}`. */
161
+ sameStory: Record<string, Json[]> | null;
153
162
  raw: Json | null;
154
163
  };
155
164
  type SearchResponse = {
@@ -200,6 +209,8 @@ type ProviderInfo = {
200
209
  url: string | null;
201
210
  normalized: boolean;
202
211
  tiers: string[];
212
+ /** Whether auto-routing can pick it; `null` from a router that does not say. */
213
+ autoRouted: boolean | null;
203
214
  };
204
215
  /** One session as `sessions.list` reports it. */
205
216
  type SessionSummary = {
@@ -243,6 +254,10 @@ type MonitorCreateOptions = {
243
254
  webhookUrl?: string;
244
255
  /** Delete the monitor after this long, such as `"12h"` or `"7d"` (at most `"30d"`). Without it, the monitor never expires. */
245
256
  ttl?: string;
257
+ /** Start the monitor agent with the monitor: it checks every result against the query
258
+ * and keeps the ones that match. Telem turns it on when you leave this out; `false`
259
+ * creates the monitor without one. Read what the agent keeps with `monitors.changes`. */
260
+ agent?: boolean;
246
261
  signal?: AbortSignal;
247
262
  };
248
263
  /** Options for `monitors.update`. Send at least one field. */
@@ -250,7 +265,7 @@ type MonitorUpdateOptions = {
250
265
  /** `"paused"` or `"active"`. */
251
266
  status?: string;
252
267
  frequency?: string;
253
- /** A new webhook URL, with a new `webhookSecret` in the response. `null` removes it. */
268
+ /** A new webhook URL, with a new `webhookSecret` in the response. `null` or `""` removes it. */
254
269
  webhookUrl?: string | null;
255
270
  signal?: AbortSignal;
256
271
  };
@@ -267,25 +282,144 @@ type Monitor = {
267
282
  id: string;
268
283
  query: string;
269
284
  frequency: string;
270
- /** `active` or `paused` (you paused it). */
285
+ /** `active`, `paused` (you paused it) or `ended` (past `expiresAt`). */
271
286
  status: string;
272
287
  sessionId: string;
273
288
  createdAt: string;
274
289
  lastPolledAt: string | null;
275
- /** When Telem deletes the monitor; `null`: never. */
290
+ /** When the monitor ends; `null`: never. */
276
291
  expiresAt: string | null;
277
292
  webhookUrl: string | null;
278
293
  /** Signs the webhook deliveries. Only in the response that set `webhookUrl`. */
279
294
  webhookSecret: string | null;
295
+ /** The monitor agent's state. `null`: the monitor has no agent. Absent when the router
296
+ * sends none, as a router from before the agent reads does. */
297
+ agentState?: AgentState | null;
298
+ raw: Json;
299
+ };
300
+ /** The latest compile of an agent's plan (`AgentState.compile`). */
301
+ type AgentCompileState = {
302
+ jobId: string;
303
+ /** The compile job's status, such as `done` or `failed`. */
304
+ status: string;
305
+ /** Why the compile failed; `null` when it did not. */
306
+ error: string | null;
307
+ };
308
+ /** The state of a monitor's agent: `monitor.agentState`. */
309
+ type AgentState = {
310
+ /** The agent engine, `"v4"` today. */
311
+ engine: string;
312
+ /** Such as `compiling`, `ready`, `paused` or `failed`. */
313
+ status: string;
314
+ /** `true` when `status` is `failed`. */
315
+ needsAttention: boolean;
316
+ /** The version of the agent's plan; `null` before it has one. */
317
+ planVersion: number | null;
318
+ /** When the agent runs next; `null` when no run is scheduled. */
319
+ nextRunAt: string | null;
320
+ /** The `seq` of the newest change `monitors.changes` can serve now. */
321
+ watermark: number;
322
+ /** The latest compile of the plan; `null` when there has been none. */
323
+ compile: AgentCompileState | null;
324
+ };
325
+ /** The monitor agent's document: `monitors.agent`. A field is there only when the
326
+ * router sent it. The rest of the document (the plan, the counts, the runs) is in `raw`. */
327
+ type AgentDocument = {
328
+ /** Such as `compiling`, `ready`, `paused`, `failed` or `ended`. */
329
+ status?: string;
330
+ query?: string;
331
+ frequency?: string;
332
+ /** The version of the plan; 0 while the agent compiles. */
333
+ specVersion?: number;
334
+ /** The agent's spending limits; `null` when none are set. */
335
+ budget?: Json | null;
336
+ /** What the agent has spent, in US dollars. */
337
+ spendUsd?: number;
280
338
  raw: Json;
281
339
  };
340
+ /** The item a change is about. `entityId` is `null` for a change to the whole monitor,
341
+ * such as a run or a plan change. */
342
+ type ChangeSubject = {
343
+ entityId: string | null;
344
+ name: string | null;
345
+ };
346
+ /** One change the monitor agent made. */
347
+ type ChangeRecord = {
348
+ /** The change's place in the stream. It grows with every change and is never reused.
349
+ * Pass the last one you handled as `cursor` to read on from there. */
350
+ seq: number;
351
+ /** The change's own type, such as `new_object`. */
352
+ type: string;
353
+ /** What happened: one of the six kinds `entity_added`, `value_changed`,
354
+ * `fact_status_changed`, `alert`, `run` or `plan_changed`. */
355
+ kind: string;
356
+ monitorId: string;
357
+ subject: ChangeSubject;
358
+ /** What it was before, as the router sent it; `null` when there is no before. Its shape
359
+ * depends on `kind`. */
360
+ before: unknown;
361
+ /** What it is now, as the router sent it. */
362
+ after: unknown;
363
+ /** The facts behind the change; empty when the router names none. */
364
+ factIds: string[];
365
+ /** When it happened, ISO 8601. */
366
+ at: string;
367
+ raw: Json;
368
+ };
369
+ /** One page of `monitors.changes`. */
370
+ type ChangesPage = {
371
+ changes: ChangeRecord[];
372
+ /** Keep this and pass it as `cursor` to read the changes after this page. On a full
373
+ * page (`limit` changes) it is the `seq` of the last change. On a shorter page, an
374
+ * empty one included, it is `watermark`: the read got that far, past the changes
375
+ * that `kinds` left out too. */
376
+ nextCursor: number;
377
+ /** The `seq` of the newest change that can be read now. You are caught up when
378
+ * `nextCursor` equals it, or when a page is empty. */
379
+ watermark: number;
380
+ };
381
+ /** Options for `monitors.changes`. */
382
+ type MonitorChangesOptions = {
383
+ /** Read the changes after this `seq`. If you leave it out, the page starts at the
384
+ * first change. Do not send it with `since`. */
385
+ cursor?: number;
386
+ /** Read from this time on: a `Date`, or an ISO 8601 time with an offset such as
387
+ * `"2026-11-03T12:00:00Z"`. Do not send it with `cursor`. */
388
+ since?: string | Date;
389
+ /** Only these kinds of change: `entity_added`, `value_changed`, `fact_status_changed`,
390
+ * `alert`, `run` or `plan_changed`, for example `["alert", "run"]`. Another name is a
391
+ * 400 (`kinds_invalid`). An empty list is no filter. */
392
+ kinds?: string[];
393
+ /** How many changes to return (server default 200, maximum 500). */
394
+ limit?: number;
395
+ signal?: AbortSignal;
396
+ };
397
+ /** Options for `monitors.iterateChanges`. */
398
+ type MonitorIterateChangesOptions = {
399
+ /** Read the changes after this `seq`. If you leave it out, the walk starts at the
400
+ * first change. */
401
+ cursor?: number;
402
+ signal?: AbortSignal;
403
+ };
404
+ /** Options for `monitors.list` and `monitors.listPage`. */
405
+ type MonitorListOptions = {
406
+ /** How many to return (server default 50, maximum 200). */
407
+ limit?: number;
408
+ /** The `nextCursor` of the page before. */
409
+ cursor?: string;
410
+ signal?: AbortSignal;
411
+ };
412
+ /** One page of `monitors.listPage`. */
413
+ type MonitorPage = {
414
+ monitors: Monitor[];
415
+ /** Pass as `cursor` to get the next page; `null` on the last page. */
416
+ nextCursor: string | null;
417
+ };
282
418
  /** The `telem.monitors` namespace. */
283
419
  type MonitorsResource = {
284
420
  create(query: string, options?: MonitorCreateOptions): Promise<Monitor>;
285
- list(options?: {
286
- limit?: number;
287
- signal?: AbortSignal;
288
- }): Promise<Monitor[]>;
421
+ list(options?: MonitorListOptions): Promise<Monitor[]>;
422
+ listPage(options?: MonitorListOptions): Promise<MonitorPage>;
289
423
  get(monitorId: string, options?: {
290
424
  signal?: AbortSignal;
291
425
  }): Promise<Monitor>;
@@ -294,6 +428,14 @@ type MonitorsResource = {
294
428
  delete(monitorId: string, options?: {
295
429
  signal?: AbortSignal;
296
430
  }): Promise<void>;
431
+ /** Read the monitor agent's document. A monitor without an agent answers 404. */
432
+ agent(monitorId: string, options?: {
433
+ signal?: AbortSignal;
434
+ }): Promise<AgentDocument>;
435
+ /** One page of the agent's changes. Keep `nextCursor` and pass it back as `cursor`. */
436
+ changes(monitorId: string, options?: MonitorChangesOptions): Promise<ChangesPage>;
437
+ /** Every change after `cursor`, page by page, until you are caught up. */
438
+ iterateChanges(monitorId: string, options?: MonitorIterateChangesOptions): AsyncGenerator<ChangeRecord, void, undefined>;
297
439
  };
298
440
 
299
441
  /**
@@ -437,7 +579,8 @@ declare class Telem {
437
579
  readonly maxRetries: number;
438
580
  /** The session read surface: `list` and `results(id)`. */
439
581
  readonly sessions: SessionsResource;
440
- /** Monitors: `create`, `list`, `get`, `update`, `poll`, `delete`. */
582
+ /** Monitors: `create`, `list`, `listPage`, `get`, `update`, `poll`, `delete`, and the
583
+ * agent reads `agent`, `changes` and `iterateChanges`. */
441
584
  readonly monitors: MonitorsResource;
442
585
  /**
443
586
  * @param options - An API key string, or an options object. Anything left unset
@@ -470,4 +613,4 @@ declare class Telem {
470
613
  providers(): Promise<ProviderInfo[]>;
471
614
  }
472
615
 
473
- export { type FetchOptions, type FetchResponse, type FetchResult, type Json, type Monitor, type MonitorCreateOptions, type MonitorPollOptions, type MonitorUpdateOptions, type MonitorsResource, type ProviderInfo, type ProviderRun, RESERVED_METADATA, type SearchDefaults, type SearchOptions, type SearchResponse, type SearchResult, type SessionResults, type SessionSummary, type SessionsResource, type SnapshotArgs, Telem, type TelemClientErrorCode, TelemError, type TelemErrorOptions, type TelemOptions, TimeoutError, type TrajectoryArgs, type TrajectoryEnvelope, type TrajectorySnapshot, buildMetadata, buildSnapshot, contentAnchor, Telem as default, eventNodeKey, fingerprint, sessionKey, snapshotNodeKey, toolMarker, verifyWebhook, windowAnchor, withTrajectory };
616
+ export { type AgentCompileState, type AgentDocument, type AgentState, type ChangeRecord, type ChangeSubject, type ChangesPage, type FetchOptions, type FetchResponse, type FetchResult, type Json, type Monitor, type MonitorChangesOptions, type MonitorCreateOptions, type MonitorIterateChangesOptions, type MonitorListOptions, type MonitorPage, type MonitorPollOptions, type MonitorUpdateOptions, type MonitorsResource, type ProviderInfo, type ProviderRun, RESERVED_METADATA, type SearchDefaults, type SearchOptions, type SearchResponse, type SearchResult, type SessionResults, type SessionSummary, type SessionsResource, type SnapshotArgs, Telem, type TelemClientErrorCode, TelemError, type TelemErrorOptions, type TelemOptions, TimeoutError, type TrajectoryArgs, type TrajectoryEnvelope, type TrajectorySnapshot, buildMetadata, buildSnapshot, contentAnchor, Telem as default, eventNodeKey, fingerprint, sessionKey, snapshotNodeKey, toolMarker, verifyWebhook, windowAnchor, withTrajectory };
package/dist/index.js CHANGED
@@ -19,10 +19,10 @@ import {
19
19
  windowAnchor,
20
20
  wireMetadata,
21
21
  withTrajectory
22
- } from "./chunk-DKTEG7EY.js";
22
+ } from "./chunk-SQ3ZZA72.js";
23
23
 
24
24
  // src/version.ts
25
- var VERSION = "0.3.2";
25
+ var VERSION = "0.4.0";
26
26
 
27
27
  // src/config.ts
28
28
  var DEFAULT_BASE_URL = "https://router.telem.ai";
@@ -82,6 +82,7 @@ function resolveConfig(options) {
82
82
  if (typeof send !== "function") {
83
83
  throw invalidOption("no fetch on this runtime: pass one with the `fetch` option");
84
84
  }
85
+ if (typeof opts.apiKey === "string" && !opts.apiKey.trim()) throw invalidOption("apiKey is empty");
85
86
  let apiKey = opts.apiKey || env.TELEM_API_KEY || void 0;
86
87
  let baseUrl = opts.baseUrl || env.TELEM_BASE_URL || void 0;
87
88
  if (apiKey === void 0 || baseUrl === void 0) {
@@ -120,12 +121,20 @@ function resolveConfig(options) {
120
121
  autoRouting: opts.autoRouting ?? (trimBlank(env.TELEM_AUTO_ROUTING ?? "") || void 0),
121
122
  maxRoutingProviders: opts.maxRoutingProviders ?? intEnv(env.TELEM_MAX_ROUTING_PROVIDERS),
122
123
  topic: opts.topic ?? (trimBlank(env.TELEM_TOPIC ?? "") || void 0)
124
+ },
125
+ // Fetch defaults: env var only, parsed as the shared option table parses it (CSV,
126
+ // a trimmed name, a "1"-only flag); the tier is lowercased, as the agent plugins
127
+ // send it. No constructor option, and no config file, as above.
128
+ fetchDefaults: {
129
+ providers: csvEnv(env.TELEM_FETCH_PROVIDERS),
130
+ tier: trimBlank(env.TELEM_FETCH_TIER ?? "").toLowerCase() || void 0,
131
+ noCache: env.TELEM_FETCH_NO_CACHE === "1" || void 0
123
132
  }
124
133
  };
125
134
  }
126
135
 
127
136
  // src/methods/fetch.ts
128
- function fetchBody(url, options) {
137
+ function fetchBody(url, options, defaults) {
129
138
  const urls = stringList(
130
139
  typeof url === "string" ? [url] : url,
131
140
  "fetch() requires at least one URL",
@@ -133,11 +142,16 @@ function fetchBody(url, options) {
133
142
  );
134
143
  const { metadata, sessionId } = wireMetadata(options, "fetch");
135
144
  const body = { urls, metadata };
145
+ const providers2 = options.providers ?? defaults.providers;
146
+ const tier = options.tier ?? defaults.tier;
147
+ const noCache = options.noCache ?? defaults.noCache;
136
148
  const block = {};
137
- if (options.providers !== void 0) block.providers = options.providers;
149
+ if (providers2 !== void 0) block.providers = providers2;
138
150
  if (options.inlineContent !== void 0) block.inline_content = options.inlineContent;
139
151
  if (options.inlineMaxChars !== void 0) block.inline_max_chars = options.inlineMaxChars;
140
152
  if (options.contentFormat !== void 0) block.content_format = options.contentFormat;
153
+ if (tier !== void 0) block.tier = tier;
154
+ if (noCache !== void 0) block.no_cache = noCache;
141
155
  if (Object.keys(block).length) body.options = block;
142
156
  if (sessionId !== void 0) body.session_id = sessionId;
143
157
  return body;
@@ -178,12 +192,17 @@ function buildFetchResponse(interaction) {
178
192
  };
179
193
  }
180
194
  async function fetchUrls(config, url, options) {
181
- const body = fetchBody(url, options);
195
+ const body = fetchBody(url, options, config.fetchDefaults);
182
196
  return buildFetchResponse(await request(config, "POST", "/v1/fetch", body, options.signal));
183
197
  }
184
198
 
185
199
  // src/methods/search.ts
186
200
  var MIN_SCHEMA_VERSION = 2;
201
+ function providersRefusal(include, includeFromCall, excludeFromCall) {
202
+ const where = (half, env, fromCall) => fromCall ? `the ${half} search() option` : `the client's ${half} or ${env}`;
203
+ const names = include.join(", ");
204
+ return `providersExclude removes every provider in providersInclude (${names}), so no provider is left and no search was sent. Remove ${names} from providersInclude (set by ${where("providersInclude", "TELEM_PROVIDERS_INCLUDE", includeFromCall)}) or from providersExclude (set by ${where("providersExclude", "TELEM_PROVIDERS_EXCLUDE", excludeFromCall)}).`;
205
+ }
187
206
  function searchBlock(defaults, options) {
188
207
  const fromCallTier = options.tier !== void 0;
189
208
  const fromCallFields = options.fields !== void 0;
@@ -197,7 +216,13 @@ function searchBlock(defaults, options) {
197
216
  let exclude = options.providersExclude ?? defaults.providersExclude;
198
217
  if (include !== void 0 && exclude !== void 0) {
199
218
  const excluded = new Set(exclude);
200
- include = include.filter((name) => !excluded.has(name));
219
+ const kept = include.filter((name) => !excluded.has(name));
220
+ if (include.length && !kept.length) {
221
+ throw invalidRequest(
222
+ providersRefusal(include, options.providersInclude !== void 0, options.providersExclude !== void 0)
223
+ );
224
+ }
225
+ include = kept;
201
226
  exclude = void 0;
202
227
  }
203
228
  const includeFullContent = options.includeFullContent ?? defaults.includeFullContent;
@@ -307,7 +332,8 @@ function providerRun(run) {
307
332
  related: envelope.related ?? null,
308
333
  verticals: envelope.verticals ?? null,
309
334
  usage: envelope.usage ?? null,
310
- warnings: asArray(envelope.warnings)
335
+ warnings: asArray(envelope.warnings),
336
+ sameStory: isObject(envelope.same_story) ? envelope.same_story : null
311
337
  };
312
338
  }
313
339
  function buildSearchResponse(interaction) {
@@ -331,9 +357,25 @@ async function search(config, query, options) {
331
357
  }
332
358
 
333
359
  // src/methods/monitors.ts
360
+ function agentStateFrom(data) {
361
+ return {
362
+ engine: String(data.engine ?? ""),
363
+ status: String(data.status ?? ""),
364
+ needsAttention: data.needs_attention === true,
365
+ planVersion: typeof data.plan_version === "number" ? data.plan_version : null,
366
+ nextRunAt: typeof data.next_run_at === "string" ? data.next_run_at : null,
367
+ watermark: Number(data.watermark ?? 0),
368
+ // No compile job yet is null, not an object of empty strings.
369
+ compile: isObject(data.compile) ? {
370
+ jobId: String(data.compile.job_id ?? ""),
371
+ status: String(data.compile.status ?? ""),
372
+ error: typeof data.compile.error === "string" ? data.compile.error : null
373
+ } : null
374
+ };
375
+ }
334
376
  function monitorFrom(body) {
335
377
  const data = asObject(body);
336
- return {
378
+ const monitor = {
337
379
  id: String(data.id ?? ""),
338
380
  query: data.query ?? "",
339
381
  frequency: data.frequency ?? "",
@@ -346,12 +388,85 @@ function monitorFrom(body) {
346
388
  webhookSecret: data.webhook_secret ?? null,
347
389
  raw: data
348
390
  };
391
+ if (data.agent_state !== void 0) {
392
+ monitor.agentState = isObject(data.agent_state) ? agentStateFrom(data.agent_state) : null;
393
+ }
394
+ return monitor;
395
+ }
396
+ function agentDocumentFrom(body) {
397
+ const data = asObject(body);
398
+ const document = { raw: data };
399
+ if (typeof data.status === "string") document.status = data.status;
400
+ if (typeof data.query === "string") document.query = data.query;
401
+ if (typeof data.frequency === "string") document.frequency = data.frequency;
402
+ if (typeof data.spec_version === "number") document.specVersion = data.spec_version;
403
+ if (data.budget !== void 0) document.budget = isObject(data.budget) ? data.budget : null;
404
+ if (typeof data.spend_usd === "number") document.spendUsd = data.spend_usd;
405
+ return document;
406
+ }
407
+ function changeRecordFrom(entry) {
408
+ const subject = asObject(entry.subject);
409
+ return {
410
+ seq: Number(entry.seq ?? 0),
411
+ type: String(entry.type ?? ""),
412
+ kind: String(entry.kind ?? ""),
413
+ monitorId: String(entry.monitor_id ?? ""),
414
+ subject: {
415
+ entityId: typeof subject.entity_id === "string" ? subject.entity_id : null,
416
+ name: typeof subject.name === "string" ? subject.name : null
417
+ },
418
+ // The values depend on the kind (an object, a status string, nothing): as sent.
419
+ before: entry.before ?? null,
420
+ after: entry.after ?? null,
421
+ factIds: asArray(entry.fact_ids).map(String),
422
+ at: String(entry.at ?? ""),
423
+ raw: entry
424
+ };
425
+ }
426
+ function changesPageFrom(body) {
427
+ const data = asObject(body);
428
+ return {
429
+ changes: asArray(data.changes).filter(isObject).map(changeRecordFrom),
430
+ nextCursor: Number(data.next_cursor ?? 0),
431
+ watermark: Number(data.watermark ?? 0)
432
+ };
433
+ }
434
+ function listQuery(options) {
435
+ const params = new URLSearchParams();
436
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
437
+ if (options.cursor !== void 0) params.set("cursor", options.cursor);
438
+ const query = params.toString();
439
+ return query ? `?${query}` : "";
440
+ }
441
+ function sinceParam(since) {
442
+ if (typeof since === "string") return since;
443
+ if (Number.isNaN(since.getTime())) throw invalidRequest("since is not a valid date");
444
+ return since.toISOString();
445
+ }
446
+ function changesQuery(options) {
447
+ const params = new URLSearchParams();
448
+ if (options.cursor !== void 0) params.set("cursor", String(options.cursor));
449
+ if (options.since !== void 0) params.set("since", sinceParam(options.since));
450
+ if (options.kinds !== void 0) params.set("kinds", options.kinds.join(","));
451
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
452
+ const query = params.toString();
453
+ return query ? `?${query}` : "";
349
454
  }
350
455
  function createMonitors(config) {
351
456
  const path = (monitorId, suffix = "") => {
352
457
  if (!monitorId || /^\.+$/.test(monitorId)) throw invalidRequest("a monitor id is required");
353
458
  return `/v1/monitors/${encodeURIComponent(monitorId)}${suffix}`;
354
459
  };
460
+ const listPage = async (options = {}) => {
461
+ const route = `/v1/monitors${listQuery(options)}`;
462
+ const data = asObject(await request(config, "GET", route, void 0, options.signal));
463
+ const nextCursor = typeof data.next_cursor === "string" ? data.next_cursor : null;
464
+ return { monitors: asArray(data.monitors).map(monitorFrom), nextCursor };
465
+ };
466
+ const changes = async (monitorId, options = {}) => {
467
+ const route = `${path(monitorId, "/agent/changes")}${changesQuery(options)}`;
468
+ return changesPageFrom(await request(config, "GET", route, void 0, options.signal));
469
+ };
355
470
  return {
356
471
  /** Create a monitor. Telem runs `query` on the schedule. */
357
472
  async create(query, options = {}) {
@@ -365,15 +480,25 @@ function createMonitors(config) {
365
480
  if (options.metadata !== void 0) body.metadata = options.metadata;
366
481
  if (options.webhookUrl !== void 0) body.webhook_url = options.webhookUrl;
367
482
  if (options.ttl !== void 0) body.ttl = options.ttl;
368
- const data = await request(config, "POST", "/v1/monitors", body, options.signal, "rate_limit");
483
+ if (options.agent !== void 0) body.agent = options.agent;
484
+ const data = await request(config, "POST", "/v1/monitors", body, options.signal, "rate_limit").catch(
485
+ (error) => {
486
+ if (error instanceof TimeoutError) {
487
+ throw new TimeoutError(
488
+ `${error.message}. The monitor may exist: list monitors before you create it again.`,
489
+ error.cause
490
+ );
491
+ }
492
+ throw error;
493
+ }
494
+ );
369
495
  return monitorFrom(data);
370
496
  },
371
- /** List your monitors, newest first (server default 50, maximum 200). */
497
+ /** List your monitors, newest first. `listPage` also gives the next cursor. */
372
498
  async list(options = {}) {
373
- const query = options.limit === void 0 ? "" : `?limit=${options.limit}`;
374
- const body = await request(config, "GET", `/v1/monitors${query}`, void 0, options.signal);
375
- return asArray(asObject(body).monitors).map(monitorFrom);
499
+ return (await listPage(options)).monitors;
376
500
  },
501
+ listPage,
377
502
  /** Get one monitor. */
378
503
  async get(monitorId, options = {}) {
379
504
  return monitorFrom(await request(config, "GET", path(monitorId), void 0, options.signal));
@@ -383,7 +508,7 @@ function createMonitors(config) {
383
508
  const body = {};
384
509
  if (options.status !== void 0) body.status = options.status;
385
510
  if (options.frequency !== void 0) body.frequency = options.frequency;
386
- if (options.webhookUrl !== void 0) body.webhook_url = options.webhookUrl;
511
+ if (options.webhookUrl !== void 0) body.webhook_url = options.webhookUrl || null;
387
512
  const route = path(monitorId);
388
513
  return monitorFrom(await request(config, "PATCH", route, body, options.signal));
389
514
  },
@@ -399,6 +524,23 @@ function createMonitors(config) {
399
524
  /** Delete a monitor. A repeat deletes what a failed delete left. */
400
525
  async delete(monitorId, options = {}) {
401
526
  await request(config, "DELETE", path(monitorId), void 0, options.signal);
527
+ },
528
+ /** Read the monitor agent's document. A monitor without an agent answers 404. */
529
+ async agent(monitorId, options = {}) {
530
+ const route = path(monitorId, "/agent");
531
+ return agentDocumentFrom(await request(config, "GET", route, void 0, options.signal));
532
+ },
533
+ /** One page of the agent's changes. Keep `nextCursor` and pass it back as `cursor`. */
534
+ changes,
535
+ /** Every change after `cursor`, page by page, until you are caught up. */
536
+ async *iterateChanges(monitorId, options = {}) {
537
+ let cursor = options.cursor ?? 0;
538
+ for (; ; ) {
539
+ const page = await changes(monitorId, { cursor, signal: options.signal });
540
+ for (const change of page.changes) yield change;
541
+ if (!page.changes.length || page.nextCursor <= cursor || page.nextCursor >= page.watermark) return;
542
+ cursor = page.nextCursor;
543
+ }
402
544
  }
403
545
  };
404
546
  }
@@ -431,7 +573,8 @@ async function providers(config) {
431
573
  method: entry.method ?? null,
432
574
  url: entry.url ?? null,
433
575
  normalized: entry.normalized ?? false,
434
- tiers: asArray(entry.tiers)
576
+ tiers: asArray(entry.tiers),
577
+ autoRouted: entry.auto_routed ?? null
435
578
  }));
436
579
  }
437
580
 
@@ -500,7 +643,8 @@ var Telem = class {
500
643
  maxRetries;
501
644
  /** The session read surface: `list` and `results(id)`. */
502
645
  sessions;
503
- /** Monitors: `create`, `list`, `get`, `update`, `poll`, `delete`. */
646
+ /** Monitors: `create`, `list`, `listPage`, `get`, `update`, `poll`, `delete`, and the
647
+ * agent reads `agent`, `changes` and `iterateChanges`. */
504
648
  monitors;
505
649
  #config;
506
650
  /**
@@ -9,7 +9,7 @@ import {
9
9
  toolMarker,
10
10
  windowAnchor,
11
11
  withTrajectory
12
- } from "../chunk-DKTEG7EY.js";
12
+ } from "../chunk-SQ3ZZA72.js";
13
13
 
14
14
  // src/vercel/index.ts
15
15
  import { jsonSchema } from "ai";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telemai/sdk",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Telem web search and fetch for JavaScript and TypeScript.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",