@diffbot/typescript 0.1.0 → 0.3.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 +196 -9
- package/dist/index.cjs +242 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +163 -9
- package/dist/index.d.ts +163 -9
- package/dist/index.js +242 -47
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +93 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +27 -0
- package/dist/node/index.d.ts +27 -0
- package/dist/node/index.js +63 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +18 -12
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ interface DiffbotClientOptions {
|
|
|
28
28
|
crawlerUrl?: string;
|
|
29
29
|
webSearchUrl?: string;
|
|
30
30
|
nlpUrl?: string;
|
|
31
|
+
dqlUrl?: string;
|
|
32
|
+
ontologyUrl?: string;
|
|
31
33
|
fetch?: FetchFn;
|
|
32
34
|
}
|
|
33
35
|
declare class DiffbotClient {
|
|
@@ -37,14 +39,21 @@ declare class DiffbotClient {
|
|
|
37
39
|
readonly crawlerUrl: string;
|
|
38
40
|
readonly webSearchUrl: string;
|
|
39
41
|
readonly nlpUrl: string;
|
|
42
|
+
readonly dqlUrl: string;
|
|
43
|
+
readonly ontologyUrl: string;
|
|
40
44
|
readonly http: HttpClient;
|
|
41
45
|
constructor(options: DiffbotClientOptions);
|
|
42
46
|
close(): Promise<void>;
|
|
43
47
|
[Symbol.asyncDispose](): Promise<void>;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
type TokenSource = object;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a Diffbot API token from an explicit arg, then a Workers-style
|
|
53
|
+
* `env` object, then `process.env`. Does not touch the filesystem — for a
|
|
54
|
+
* credentials-file fallback, use `resolveToken` from `@diffbot/typescript/node`.
|
|
55
|
+
*/
|
|
56
|
+
declare function resolveTokenFromEnv(token?: string | null, env?: TokenSource): string;
|
|
48
57
|
|
|
49
58
|
type JsonObject = Record<string, unknown>;
|
|
50
59
|
|
|
@@ -54,7 +63,46 @@ interface ChatMessage {
|
|
|
54
63
|
role: string;
|
|
55
64
|
content: string;
|
|
56
65
|
}
|
|
57
|
-
|
|
66
|
+
/** `response_format` types accepted by the Diffbot LLM endpoint. */
|
|
67
|
+
declare const RESPONSE_FORMAT_TYPES: readonly ["text", "json_object", "json_schema"];
|
|
68
|
+
type ResponseFormatType = (typeof RESPONSE_FORMAT_TYPES)[number];
|
|
69
|
+
interface ResponseFormat {
|
|
70
|
+
type: ResponseFormatType;
|
|
71
|
+
json_schema?: {
|
|
72
|
+
name?: string;
|
|
73
|
+
schema: JsonObject;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
interface AskOptions {
|
|
77
|
+
/**
|
|
78
|
+
* Constrain the output, e.g. `{ type: "json_object" }` or `jsonSchemaFormat(schema)`.
|
|
79
|
+
* Prefer a schema: `json_object` applies no server-side grammar.
|
|
80
|
+
*/
|
|
81
|
+
responseFormat?: ResponseFormat;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build a `response_format` that constrains output to `schema`. The endpoint requires
|
|
85
|
+
* the schema nested under `json_schema.schema`; anywhere else is ignored server-side
|
|
86
|
+
* without an error, so prefer this helper over hand-building the object.
|
|
87
|
+
*/
|
|
88
|
+
declare function jsonSchemaFormat(schema: JsonObject, name?: string): ResponseFormat;
|
|
89
|
+
/** Parse the model's final answer as JSON, tolerating think blocks and fences. */
|
|
90
|
+
declare function extractJson(text: string): unknown;
|
|
91
|
+
/**
|
|
92
|
+
* Schema used when the caller wants JSON but has no shape in mind. This goes through
|
|
93
|
+
* the json_schema path rather than `{ type: "json_object" }` on purpose: json_object
|
|
94
|
+
* applies no server-side grammar, so the RAG loop's internal <functioncall> JSON
|
|
95
|
+
* satisfies it and gets returned as the final answer. That is reproducible whenever
|
|
96
|
+
* the request carries a system message.
|
|
97
|
+
*/
|
|
98
|
+
declare const ANY_OBJECT_SCHEMA: JsonObject;
|
|
99
|
+
declare function ask(client: DiffbotClient, messages: ChatMessage[], options?: AskOptions): AsyncGenerator<string>;
|
|
100
|
+
/**
|
|
101
|
+
* Ask the Diffbot LLM and return the answer parsed as JSON. With `schema` (a JSON Schema
|
|
102
|
+
* object) the model is constrained to it by grammar; without one it falls back to
|
|
103
|
+
* unconstrained JSON output. Pass `responseFormat` instead of `schema` for full control.
|
|
104
|
+
*/
|
|
105
|
+
declare function askJson<T = unknown>(client: DiffbotClient, messages: ChatMessage[], schema?: JsonObject, options?: AskOptions): Promise<T>;
|
|
58
106
|
|
|
59
107
|
declare enum CrawlEventType {
|
|
60
108
|
JobCreated = "job_created",
|
|
@@ -92,7 +140,6 @@ declare class Ontology {
|
|
|
92
140
|
readonly data: JsonObject;
|
|
93
141
|
constructor(data: JsonObject);
|
|
94
142
|
static fromJson(raw: string | Uint8Array): Ontology;
|
|
95
|
-
static fromPath(path: string): Promise<Ontology>;
|
|
96
143
|
types(): string[];
|
|
97
144
|
composites(): string[];
|
|
98
145
|
enums(): string[];
|
|
@@ -119,15 +166,122 @@ declare function dql(client: DiffbotClient, query: string, options?: DqlOptions)
|
|
|
119
166
|
declare function dqlParallel(client: DiffbotClient, queries: Array<DqlOptions & {
|
|
120
167
|
query: string;
|
|
121
168
|
}>, workers?: number): Promise<Array<JsonObject | Uint8Array>>;
|
|
122
|
-
|
|
169
|
+
/** Fetch the raw ontology JSON text. For a parsed, cached `Ontology`, see `OntologyStore`. */
|
|
170
|
+
declare function dqlFetchOntologyText(client: DiffbotClient): Promise<string>;
|
|
123
171
|
declare function dqlFetchOntology(client: DiffbotClient): Promise<Ontology>;
|
|
124
172
|
|
|
125
173
|
declare function entities(client: DiffbotClient, text: string, lang?: string): Promise<JsonObject>;
|
|
126
174
|
|
|
127
|
-
|
|
175
|
+
interface OntologyStoreOptions {
|
|
176
|
+
/**
|
|
177
|
+
* How long a cached entry should be kept, in seconds. Default 86_400 (24h).
|
|
178
|
+
* The base `OntologyStore` doesn't expire its in-memory cache on its own —
|
|
179
|
+
* an explicit `refresh: true` is the only way to bust it — so this only
|
|
180
|
+
* takes effect in subclasses backed by a store with real expiration, like
|
|
181
|
+
* `KVOntologyStore` (where it becomes the KV entry's `expirationTtl`).
|
|
182
|
+
*/
|
|
183
|
+
ttlSeconds?: number;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Fetches and caches the Diffbot KG ontology for a client. The base class
|
|
187
|
+
* caches only in memory, for the lifetime of the instance; subclasses
|
|
188
|
+
* override `read`/`write` to back the cache with a durable store (a
|
|
189
|
+
* filesystem, a KV namespace, ...).
|
|
190
|
+
*/
|
|
191
|
+
declare class OntologyStore {
|
|
192
|
+
readonly client: DiffbotClient;
|
|
193
|
+
protected readonly ttlSeconds: number;
|
|
194
|
+
private inFlight;
|
|
195
|
+
constructor(client: DiffbotClient, options?: OntologyStoreOptions);
|
|
196
|
+
/**
|
|
197
|
+
* Resolve the ontology, from cache when possible. Concurrent calls share
|
|
198
|
+
* one in-flight fetch rather than issuing duplicate requests; a rejected
|
|
199
|
+
* fetch clears the in-flight memo so the next call retries instead of
|
|
200
|
+
* replaying the same failure forever.
|
|
201
|
+
*/
|
|
202
|
+
load(options?: {
|
|
203
|
+
refresh?: boolean;
|
|
204
|
+
}): Promise<Ontology>;
|
|
205
|
+
private resolve;
|
|
206
|
+
/** Read the cached ontology text, or `undefined` on a miss. Memory-only by default. */
|
|
207
|
+
protected read(): Promise<string | undefined>;
|
|
208
|
+
/** Persist ontology text after a fetch. No-op by default. */
|
|
209
|
+
protected write(_text: string): Promise<void>;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Structurally typed rather than importing `@cloudflare/workers-types`, so
|
|
213
|
+
* the SDK takes no Cloudflare dependency — a `KVNamespace` binding satisfies
|
|
214
|
+
* this directly.
|
|
215
|
+
*/
|
|
216
|
+
interface KVLike {
|
|
217
|
+
get(key: string): Promise<string | null>;
|
|
218
|
+
put(key: string, value: string, options?: {
|
|
219
|
+
expirationTtl?: number;
|
|
220
|
+
}): Promise<void>;
|
|
221
|
+
delete(key: string): Promise<void>;
|
|
222
|
+
}
|
|
223
|
+
interface KVOntologyStoreOptions extends OntologyStoreOptions {
|
|
224
|
+
/** Default `"diffbot:ontology:v1"`. Bump the suffix to force cache invalidation on a schema change. */
|
|
225
|
+
key?: string;
|
|
226
|
+
/**
|
|
227
|
+
* After this many seconds, a cached entry is served immediately while a
|
|
228
|
+
* refresh runs behind it (stale-while-revalidate) instead of being trusted
|
|
229
|
+
* outright. 0 (default) disables SWR — a cached entry is trusted for its
|
|
230
|
+
* full `ttlSeconds`. Should be less than `ttlSeconds`, or the KV entry
|
|
231
|
+
* expires before it is ever considered stale.
|
|
232
|
+
*/
|
|
233
|
+
staleAfterSeconds?: number;
|
|
234
|
+
/**
|
|
235
|
+
* TTL on the refresh lock that prevents concurrent refetches (cron, SWR,
|
|
236
|
+
* manual). Default 60s — Cloudflare KV rejects `expirationTtl` below 60.
|
|
237
|
+
*/
|
|
238
|
+
lockTtlSeconds?: number;
|
|
239
|
+
/**
|
|
240
|
+
* `ExecutionContext.waitUntil`. Without it, a background stale-while-revalidate
|
|
241
|
+
* refresh is killed as soon as the response is returned, so SWR degrades to a
|
|
242
|
+
* blocking refresh-on-expiry.
|
|
243
|
+
*/
|
|
244
|
+
waitUntil?: (promise: Promise<unknown>) => void;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* KV-backed ontology cache for Cloudflare Workers. Ports the stampede
|
|
248
|
+
* protection and stale-while-revalidate behavior of a production Worker's
|
|
249
|
+
* hand-rolled ontology cache: a short-lived lock key prevents concurrent
|
|
250
|
+
* refreshes from cron, SWR, and a manual refresh all refetching at once, and
|
|
251
|
+
* a `refreshed_at` companion key lets a stale entry be served immediately
|
|
252
|
+
* while a refresh runs in the background.
|
|
253
|
+
*/
|
|
254
|
+
declare class KVOntologyStore extends OntologyStore {
|
|
255
|
+
private readonly kv;
|
|
256
|
+
private readonly key;
|
|
257
|
+
private readonly refreshedAtKey;
|
|
258
|
+
private readonly lockKey;
|
|
259
|
+
private readonly staleAfterSeconds;
|
|
260
|
+
private readonly lockTtlSeconds;
|
|
261
|
+
private readonly waitUntil;
|
|
262
|
+
constructor(client: DiffbotClient, kv: KVLike, options?: KVOntologyStoreOptions);
|
|
263
|
+
load(options?: {
|
|
264
|
+
refresh?: boolean;
|
|
265
|
+
}): Promise<Ontology>;
|
|
266
|
+
private loadWithStaleWhileRevalidate;
|
|
267
|
+
protected read(): Promise<string | undefined>;
|
|
268
|
+
protected write(text: string): Promise<void>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** How much of each page's matching content the web search returns in `content`. */
|
|
272
|
+
type SnippetLength = "short" | "medium" | "long" | "full";
|
|
273
|
+
declare const SNIPPET_LENGTHS: readonly SnippetLength[];
|
|
274
|
+
interface WebSearchOptions {
|
|
128
275
|
numResults?: number;
|
|
129
276
|
maxTokens?: number;
|
|
130
|
-
|
|
277
|
+
/**
|
|
278
|
+
* `short` keeps a result to ~1-1.5 KB; `long` is the API default; `full` returns the
|
|
279
|
+
* whole stored page and is meant for `url:<URL>` queries. The reranker always chunks,
|
|
280
|
+
* so `full` also sends `rerank=none` and query terms after the URL are ignored.
|
|
281
|
+
*/
|
|
282
|
+
snippetLength?: SnippetLength;
|
|
283
|
+
}
|
|
284
|
+
declare function webSearch(client: DiffbotClient, text: string, options?: WebSearchOptions): Promise<JsonObject>;
|
|
131
285
|
|
|
132
286
|
/** Exception hierarchy for the Diffbot SDK. */
|
|
133
287
|
declare class DiffbotError extends Error {
|
|
@@ -158,6 +312,6 @@ declare class ExtractionError extends DiffbotError {
|
|
|
158
312
|
constructor(errorCode: number, error: string);
|
|
159
313
|
}
|
|
160
314
|
|
|
161
|
-
declare const VERSION = "0.
|
|
315
|
+
declare const VERSION = "0.3.0";
|
|
162
316
|
|
|
163
|
-
export { APIError, AuthError, type ChatMessage, type CrawlEvent, CrawlEventType, type CrawlOptions, DiffbotClient, type DiffbotClientOptions, DiffbotError, type DqlOptions, ExtractionError, type FetchFn, type JsonObject, Ontology, RateLimitError, VERSION, ValidationError, ask, crawl, crawlDeleteJob, crawlGetJob, crawlListJobs, dql, dqlFetchOntology,
|
|
317
|
+
export { ANY_OBJECT_SCHEMA, APIError, type AskOptions, AuthError, type ChatMessage, type CrawlEvent, CrawlEventType, type CrawlOptions, DiffbotClient, type DiffbotClientOptions, DiffbotError, type DqlOptions, ExtractionError, type FetchFn, type JsonObject, type KVLike, KVOntologyStore, type KVOntologyStoreOptions, Ontology, OntologyStore, type OntologyStoreOptions, RESPONSE_FORMAT_TYPES, RateLimitError, type ResponseFormat, type ResponseFormatType, SNIPPET_LENGTHS, type SnippetLength, type TokenSource, VERSION, ValidationError, type WebSearchOptions, ask, askJson, crawl, crawlDeleteJob, crawlGetJob, crawlListJobs, dql, dqlFetchOntology, dqlFetchOntologyText, dqlParallel, entities, extract, extractJson, jsonSchemaFormat, resolveTokenFromEnv, webSearch };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/constants.ts
|
|
2
|
-
var VERSION = "0.
|
|
2
|
+
var VERSION = "0.3.0";
|
|
3
3
|
var DEFAULT_TIMEOUT = 3e4;
|
|
4
4
|
var EXTRACT_BASE = "https://api.diffbot.com/v3";
|
|
5
5
|
var CRAWL_BASE = "https://api.diffbot.com/v3/crawl";
|
|
@@ -140,6 +140,8 @@ var DiffbotClient = class {
|
|
|
140
140
|
crawlerUrl;
|
|
141
141
|
webSearchUrl;
|
|
142
142
|
nlpUrl;
|
|
143
|
+
dqlUrl;
|
|
144
|
+
ontologyUrl;
|
|
143
145
|
http;
|
|
144
146
|
constructor(options) {
|
|
145
147
|
if (!options.token) {
|
|
@@ -151,6 +153,8 @@ var DiffbotClient = class {
|
|
|
151
153
|
this.crawlerUrl = options.crawlerUrl ?? CRAWL_BASE;
|
|
152
154
|
this.webSearchUrl = options.webSearchUrl ?? WEB_SEARCH_BASE;
|
|
153
155
|
this.nlpUrl = options.nlpUrl ?? NLP_BASE;
|
|
156
|
+
this.dqlUrl = options.dqlUrl ?? KG_DQL_ENDPOINT;
|
|
157
|
+
this.ontologyUrl = options.ontologyUrl ?? KG_ONTOLOGY_ENDPOINT;
|
|
154
158
|
this.http = new HttpClient(
|
|
155
159
|
options.fetch ?? fetch,
|
|
156
160
|
options.timeout ?? DEFAULT_TIMEOUT,
|
|
@@ -166,34 +170,20 @@ var DiffbotClient = class {
|
|
|
166
170
|
};
|
|
167
171
|
|
|
168
172
|
// src/auth.ts
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return "";
|
|
178
|
-
}
|
|
179
|
-
const text = readFileSync(path, "utf8");
|
|
180
|
-
for (const line of text.split("\n")) {
|
|
181
|
-
const trimmed = line.trim();
|
|
182
|
-
if (trimmed.startsWith(`${TOKEN_ENV_VAR}=`)) {
|
|
183
|
-
return trimmed.slice(TOKEN_ENV_VAR.length + 1).trim();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return "";
|
|
173
|
+
function envToken(env) {
|
|
174
|
+
const fromEnv = env?.[TOKEN_ENV_VAR];
|
|
175
|
+
if (typeof fromEnv === "string" && fromEnv.trim()) {
|
|
176
|
+
return fromEnv.trim();
|
|
177
|
+
}
|
|
178
|
+
const proc = globalThis.process;
|
|
179
|
+
const fromProcess = proc?.env?.[TOKEN_ENV_VAR];
|
|
180
|
+
return fromProcess?.trim() ?? "";
|
|
187
181
|
}
|
|
188
|
-
function
|
|
182
|
+
function resolveTokenFromEnv(token, env) {
|
|
189
183
|
if (token?.trim()) {
|
|
190
184
|
return token.trim();
|
|
191
185
|
}
|
|
192
|
-
|
|
193
|
-
if (envToken) {
|
|
194
|
-
return envToken;
|
|
195
|
-
}
|
|
196
|
-
return readCredentialsFile();
|
|
186
|
+
return envToken(env);
|
|
197
187
|
}
|
|
198
188
|
|
|
199
189
|
// src/extract.ts
|
|
@@ -231,6 +221,80 @@ async function extract(client, url, api = "analyze", fmt = "markdown") {
|
|
|
231
221
|
}
|
|
232
222
|
|
|
233
223
|
// src/ask.ts
|
|
224
|
+
var MODEL = "diffbot-small-xl";
|
|
225
|
+
var RESPONSE_FORMAT_TYPES = ["text", "json_object", "json_schema"];
|
|
226
|
+
function jsonSchemaFormat(schema, name = "response") {
|
|
227
|
+
if (schema === null || typeof schema !== "object" || Array.isArray(schema)) {
|
|
228
|
+
throw new ValidationError("schema must be a JSON Schema object");
|
|
229
|
+
}
|
|
230
|
+
return { type: "json_schema", json_schema: { name, schema } };
|
|
231
|
+
}
|
|
232
|
+
function validateResponseFormat(responseFormat) {
|
|
233
|
+
if (responseFormat === void 0) return;
|
|
234
|
+
if (responseFormat === null || typeof responseFormat !== "object" || Array.isArray(responseFormat)) {
|
|
235
|
+
throw new ValidationError("responseFormat must be an object");
|
|
236
|
+
}
|
|
237
|
+
const fmtType = responseFormat.type ?? "text";
|
|
238
|
+
if (!RESPONSE_FORMAT_TYPES.includes(fmtType)) {
|
|
239
|
+
throw new ValidationError(
|
|
240
|
+
`responseFormat type must be one of ${RESPONSE_FORMAT_TYPES.join(", ")}; got ${JSON.stringify(fmtType)}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
if (fmtType === "json_schema") {
|
|
244
|
+
const jsonSchema = responseFormat.json_schema;
|
|
245
|
+
if (jsonSchema === null || typeof jsonSchema !== "object" || !("schema" in jsonSchema)) {
|
|
246
|
+
throw new ValidationError(
|
|
247
|
+
'responseFormat { type: "json_schema" } requires the schema nested as { json_schema: { schema: {...} } }. Without it the server returns 200 and ignores the constraint. Use jsonSchemaFormat(schema) to build it.'
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
var THINK_BLOCK = /<think>[\s\S]*?<\/think>/g;
|
|
253
|
+
var JSON_FENCE = /^```(?:json)?\s*|\s*```$/gm;
|
|
254
|
+
function extractJson(text) {
|
|
255
|
+
let cleaned = text.replace(THINK_BLOCK, "").trim();
|
|
256
|
+
cleaned = cleaned.replace(JSON_FENCE, "").trim();
|
|
257
|
+
try {
|
|
258
|
+
return JSON.parse(cleaned);
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
const spans = [];
|
|
262
|
+
for (const [opener, closer] of [
|
|
263
|
+
["{", "}"],
|
|
264
|
+
["[", "]"]
|
|
265
|
+
]) {
|
|
266
|
+
const start = cleaned.indexOf(opener);
|
|
267
|
+
const end = cleaned.lastIndexOf(closer);
|
|
268
|
+
if (start !== -1 && end > start) spans.push([start, cleaned.slice(start, end + 1)]);
|
|
269
|
+
}
|
|
270
|
+
spans.sort((a, b) => a[0] - b[0]);
|
|
271
|
+
for (const [, span] of spans) {
|
|
272
|
+
try {
|
|
273
|
+
return JSON.parse(span);
|
|
274
|
+
} catch {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
throw new ValidationError(
|
|
279
|
+
`could not parse JSON from the model response: ${JSON.stringify(text.slice(0, 200))}`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
var ANY_OBJECT_SCHEMA = { type: "object" };
|
|
283
|
+
function resolveFormat(schema, responseFormat) {
|
|
284
|
+
if (schema !== void 0 && responseFormat !== void 0) {
|
|
285
|
+
throw new ValidationError("pass either schema or responseFormat, not both");
|
|
286
|
+
}
|
|
287
|
+
if (responseFormat !== void 0) return responseFormat;
|
|
288
|
+
return jsonSchemaFormat(schema ?? ANY_OBJECT_SCHEMA);
|
|
289
|
+
}
|
|
290
|
+
function checkToolCallLeak(parsed) {
|
|
291
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) && parsed.name === "functioncall" && "arguments" in parsed) {
|
|
292
|
+
throw new ValidationError(
|
|
293
|
+
'the model returned its internal tool call instead of an answer; this happens with responseFormat { type: "json_object" } because the server applies no grammar to it. Pass a schema instead.'
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return parsed;
|
|
297
|
+
}
|
|
234
298
|
function parseChunk(line) {
|
|
235
299
|
try {
|
|
236
300
|
const chunk = JSON.parse(line.replace(/^data: /, ""));
|
|
@@ -261,9 +325,11 @@ async function* linesFromStream(body) {
|
|
|
261
325
|
}
|
|
262
326
|
if (buffer) yield buffer;
|
|
263
327
|
}
|
|
264
|
-
async function* ask(client, messages) {
|
|
328
|
+
async function* ask(client, messages, options = {}) {
|
|
329
|
+
validateResponseFormat(options.responseFormat);
|
|
265
330
|
const headers = { Authorization: `Bearer ${client.token}` };
|
|
266
|
-
const payload = { model:
|
|
331
|
+
const payload = { model: MODEL, messages, stream: true };
|
|
332
|
+
if (options.responseFormat !== void 0) payload.response_format = options.responseFormat;
|
|
267
333
|
const response = await client.http.post(client.llmUrl, { headers, json: payload });
|
|
268
334
|
if (!response.ok) {
|
|
269
335
|
const body = await readBodyText(response);
|
|
@@ -283,6 +349,12 @@ async function* ask(client, messages) {
|
|
|
283
349
|
if (content) yield content;
|
|
284
350
|
}
|
|
285
351
|
}
|
|
352
|
+
async function askJson(client, messages, schema, options = {}) {
|
|
353
|
+
const responseFormat = resolveFormat(schema, options.responseFormat);
|
|
354
|
+
let text = "";
|
|
355
|
+
for await (const chunk of ask(client, messages, { responseFormat })) text += chunk;
|
|
356
|
+
return checkToolCallLeak(extractJson(text));
|
|
357
|
+
}
|
|
286
358
|
|
|
287
359
|
// src/crawl.ts
|
|
288
360
|
var CrawlEventType = /* @__PURE__ */ ((CrawlEventType2) => {
|
|
@@ -422,12 +494,7 @@ async function crawlDeleteJob(client, jobName) {
|
|
|
422
494
|
raiseForStatus(response, body);
|
|
423
495
|
}
|
|
424
496
|
|
|
425
|
-
// src/kg.ts
|
|
426
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
427
|
-
import { dirname } from "path";
|
|
428
|
-
|
|
429
497
|
// src/ontology.ts
|
|
430
|
-
import { readFile } from "fs/promises";
|
|
431
498
|
var Ontology = class _Ontology {
|
|
432
499
|
data;
|
|
433
500
|
constructor(data) {
|
|
@@ -437,10 +504,6 @@ var Ontology = class _Ontology {
|
|
|
437
504
|
const text = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
438
505
|
return new _Ontology(JSON.parse(text));
|
|
439
506
|
}
|
|
440
|
-
static async fromPath(path) {
|
|
441
|
-
const text = await readFile(path, "utf8");
|
|
442
|
-
return _Ontology.fromJson(text);
|
|
443
|
-
}
|
|
444
507
|
types() {
|
|
445
508
|
const types = this.data.types;
|
|
446
509
|
return Object.keys(types ?? {}).sort();
|
|
@@ -553,7 +616,7 @@ function buildDqlParams(client, query, options) {
|
|
|
553
616
|
}
|
|
554
617
|
async function dql(client, query, options = {}) {
|
|
555
618
|
const params = buildDqlParams(client, query, options);
|
|
556
|
-
const response = await client.http.get(
|
|
619
|
+
const response = await client.http.get(client.dqlUrl, { params });
|
|
557
620
|
const body = await readBodyText(response);
|
|
558
621
|
raiseForStatus(response, body);
|
|
559
622
|
if (options.raw) {
|
|
@@ -577,18 +640,14 @@ async function dqlParallel(client, queries, workers = 8) {
|
|
|
577
640
|
await Promise.all(Array.from({ length: limit }, () => worker()));
|
|
578
641
|
return results;
|
|
579
642
|
}
|
|
580
|
-
async function
|
|
581
|
-
const response = await client.http.get(
|
|
643
|
+
async function dqlFetchOntologyText(client) {
|
|
644
|
+
const response = await client.http.get(client.ontologyUrl);
|
|
582
645
|
const body = await readBodyText(response);
|
|
583
646
|
raiseForStatus(response, body);
|
|
584
|
-
|
|
585
|
-
await writeFile(dest, body);
|
|
647
|
+
return body;
|
|
586
648
|
}
|
|
587
649
|
async function dqlFetchOntology(client) {
|
|
588
|
-
|
|
589
|
-
const body = await readBodyText(response);
|
|
590
|
-
raiseForStatus(response, body);
|
|
591
|
-
return Ontology.fromJson(body);
|
|
650
|
+
return Ontology.fromJson(await dqlFetchOntologyText(client));
|
|
592
651
|
}
|
|
593
652
|
|
|
594
653
|
// src/nlp.ts
|
|
@@ -603,40 +662,176 @@ async function entities(client, text, lang = "auto") {
|
|
|
603
662
|
return Array.isArray(data) ? data[0] ?? {} : data;
|
|
604
663
|
}
|
|
605
664
|
|
|
665
|
+
// src/ontology-store.ts
|
|
666
|
+
var OntologyStore = class {
|
|
667
|
+
client;
|
|
668
|
+
ttlSeconds;
|
|
669
|
+
inFlight = null;
|
|
670
|
+
constructor(client, options = {}) {
|
|
671
|
+
this.client = client;
|
|
672
|
+
this.ttlSeconds = options.ttlSeconds ?? 86400;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Resolve the ontology, from cache when possible. Concurrent calls share
|
|
676
|
+
* one in-flight fetch rather than issuing duplicate requests; a rejected
|
|
677
|
+
* fetch clears the in-flight memo so the next call retries instead of
|
|
678
|
+
* replaying the same failure forever.
|
|
679
|
+
*/
|
|
680
|
+
load(options = {}) {
|
|
681
|
+
if (options.refresh) {
|
|
682
|
+
this.inFlight = null;
|
|
683
|
+
}
|
|
684
|
+
if (!this.inFlight) {
|
|
685
|
+
this.inFlight = this.resolve(options.refresh ?? false).catch((error) => {
|
|
686
|
+
this.inFlight = null;
|
|
687
|
+
throw error;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
return this.inFlight;
|
|
691
|
+
}
|
|
692
|
+
async resolve(refresh) {
|
|
693
|
+
if (!refresh) {
|
|
694
|
+
const cached = await this.read();
|
|
695
|
+
if (cached !== void 0) {
|
|
696
|
+
try {
|
|
697
|
+
return Ontology.fromJson(cached);
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
const text = await dqlFetchOntologyText(this.client);
|
|
703
|
+
await this.write(text);
|
|
704
|
+
return Ontology.fromJson(text);
|
|
705
|
+
}
|
|
706
|
+
/** Read the cached ontology text, or `undefined` on a miss. Memory-only by default. */
|
|
707
|
+
read() {
|
|
708
|
+
return Promise.resolve(void 0);
|
|
709
|
+
}
|
|
710
|
+
/** Persist ontology text after a fetch. No-op by default. */
|
|
711
|
+
write(_text) {
|
|
712
|
+
return Promise.resolve();
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
var KVOntologyStore = class extends OntologyStore {
|
|
716
|
+
kv;
|
|
717
|
+
key;
|
|
718
|
+
refreshedAtKey;
|
|
719
|
+
lockKey;
|
|
720
|
+
staleAfterSeconds;
|
|
721
|
+
lockTtlSeconds;
|
|
722
|
+
waitUntil;
|
|
723
|
+
constructor(client, kv, options = {}) {
|
|
724
|
+
super(client, options);
|
|
725
|
+
this.kv = kv;
|
|
726
|
+
this.key = options.key ?? "diffbot:ontology:v1";
|
|
727
|
+
this.refreshedAtKey = `${this.key}:refreshed_at`;
|
|
728
|
+
this.lockKey = `${this.key}:lock`;
|
|
729
|
+
this.staleAfterSeconds = options.staleAfterSeconds ?? 0;
|
|
730
|
+
this.lockTtlSeconds = options.lockTtlSeconds ?? 60;
|
|
731
|
+
this.waitUntil = options.waitUntil;
|
|
732
|
+
}
|
|
733
|
+
load(options = {}) {
|
|
734
|
+
if (!options.refresh && this.staleAfterSeconds > 0) {
|
|
735
|
+
return this.loadWithStaleWhileRevalidate();
|
|
736
|
+
}
|
|
737
|
+
return super.load(options);
|
|
738
|
+
}
|
|
739
|
+
async loadWithStaleWhileRevalidate() {
|
|
740
|
+
const [cached, refreshedAt] = await Promise.all([
|
|
741
|
+
this.kv.get(this.key),
|
|
742
|
+
this.kv.get(this.refreshedAtKey)
|
|
743
|
+
]);
|
|
744
|
+
if (cached === null) {
|
|
745
|
+
return super.load();
|
|
746
|
+
}
|
|
747
|
+
const age = refreshedAt ? (Date.now() - Number(refreshedAt)) / 1e3 : Infinity;
|
|
748
|
+
const isStale = age > this.staleAfterSeconds;
|
|
749
|
+
const ontology = Ontology.fromJson(cached);
|
|
750
|
+
if (isStale) {
|
|
751
|
+
const refresh = super.load({ refresh: true }).catch(() => {
|
|
752
|
+
});
|
|
753
|
+
if (this.waitUntil) {
|
|
754
|
+
this.waitUntil(refresh);
|
|
755
|
+
} else {
|
|
756
|
+
await refresh;
|
|
757
|
+
return super.load();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return ontology;
|
|
761
|
+
}
|
|
762
|
+
async read() {
|
|
763
|
+
const cached = await this.kv.get(this.key);
|
|
764
|
+
return cached ?? void 0;
|
|
765
|
+
}
|
|
766
|
+
async write(text) {
|
|
767
|
+
if (await this.kv.get(this.lockKey)) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
await this.kv.put(this.lockKey, "1", { expirationTtl: this.lockTtlSeconds });
|
|
771
|
+
try {
|
|
772
|
+
await this.kv.put(this.key, text, { expirationTtl: this.ttlSeconds });
|
|
773
|
+
await this.kv.put(this.refreshedAtKey, String(Date.now()), {
|
|
774
|
+
expirationTtl: this.ttlSeconds
|
|
775
|
+
});
|
|
776
|
+
} finally {
|
|
777
|
+
await this.kv.delete(this.lockKey);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
|
|
606
782
|
// src/web-search.ts
|
|
783
|
+
var SNIPPET_LENGTHS = ["short", "medium", "long", "full"];
|
|
607
784
|
async function webSearch(client, text, options = {}) {
|
|
608
785
|
const headers = { Authorization: `Bearer ${client.token}` };
|
|
609
786
|
const params = { text };
|
|
610
787
|
if (options.numResults !== void 0) params.size = options.numResults;
|
|
611
788
|
if (options.maxTokens !== void 0) params.maxTokens = options.maxTokens;
|
|
789
|
+
if (options.snippetLength !== void 0) {
|
|
790
|
+
const length = options.snippetLength.toLowerCase();
|
|
791
|
+
if (!SNIPPET_LENGTHS.includes(length)) {
|
|
792
|
+
throw new ValidationError(
|
|
793
|
+
`snippetLength must be one of ${SNIPPET_LENGTHS.join(", ")}; got ${JSON.stringify(options.snippetLength)}`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
params.snippetLength = length.toUpperCase();
|
|
797
|
+
if (length === "full") params.rerank = "none";
|
|
798
|
+
}
|
|
612
799
|
const response = await client.http.get(client.webSearchUrl, { headers, params });
|
|
613
800
|
const body = await readBodyText(response);
|
|
614
801
|
raiseForStatus(response, body);
|
|
615
802
|
return JSON.parse(body);
|
|
616
803
|
}
|
|
617
804
|
export {
|
|
805
|
+
ANY_OBJECT_SCHEMA,
|
|
618
806
|
APIError,
|
|
619
807
|
AuthError,
|
|
620
808
|
CrawlEventType,
|
|
621
809
|
DiffbotClient,
|
|
622
810
|
DiffbotError,
|
|
623
811
|
ExtractionError,
|
|
812
|
+
KVOntologyStore,
|
|
624
813
|
Ontology,
|
|
814
|
+
OntologyStore,
|
|
815
|
+
RESPONSE_FORMAT_TYPES,
|
|
625
816
|
RateLimitError,
|
|
817
|
+
SNIPPET_LENGTHS,
|
|
626
818
|
VERSION,
|
|
627
819
|
ValidationError,
|
|
628
820
|
ask,
|
|
821
|
+
askJson,
|
|
629
822
|
crawl,
|
|
630
823
|
crawlDeleteJob,
|
|
631
824
|
crawlGetJob,
|
|
632
825
|
crawlListJobs,
|
|
633
826
|
dql,
|
|
634
827
|
dqlFetchOntology,
|
|
828
|
+
dqlFetchOntologyText,
|
|
635
829
|
dqlParallel,
|
|
636
|
-
dqlRefreshOntology,
|
|
637
830
|
entities,
|
|
638
831
|
extract,
|
|
639
|
-
|
|
832
|
+
extractJson,
|
|
833
|
+
jsonSchemaFormat,
|
|
834
|
+
resolveTokenFromEnv,
|
|
640
835
|
webSearch
|
|
641
836
|
};
|
|
642
837
|
//# sourceMappingURL=index.js.map
|