@effect-uai/jina 0.9.0 → 0.10.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/dist/JinaReader.d.mts +50 -0
- package/dist/JinaReader.d.mts.map +1 -0
- package/dist/JinaReader.mjs +130 -0
- package/dist/JinaReader.mjs.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/models.d.mts +7 -1
- package/dist/models.d.mts.map +1 -1
- package/package.json +9 -4
- package/src/JinaReader.ts +198 -0
- package/src/index.ts +1 -0
- package/src/models.ts +12 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { JinaEngine } from "./models.mjs";
|
|
2
|
+
import { Context, Effect, Layer, Redacted } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { CommonReadRequest, ReadResponse, WebRead } from "@effect-uai/core/WebRead";
|
|
6
|
+
|
|
7
|
+
//#region src/JinaReader.d.ts
|
|
8
|
+
declare namespace JinaReader_d_exports {
|
|
9
|
+
export { Config, JinaReadRequest, JinaReader, JinaReaderService, layer, make };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Jina-typed read request. Widens {@link CommonReadRequest} with Jina
|
|
13
|
+
* Reader's own header knobs; the common fields map onto Reader headers (see
|
|
14
|
+
* the codec below).
|
|
15
|
+
*/
|
|
16
|
+
type JinaReadRequest = CommonReadRequest & {
|
|
17
|
+
/** Fetching engine - see {@link JinaEngine}. */readonly engine?: JinaEngine; /** CSS selector to extract instead of the whole page (`X-Target-Selector`). */
|
|
18
|
+
readonly targetSelector?: string; /** Bypass Jina's page cache (`X-No-Cache`). */
|
|
19
|
+
readonly noCache?: boolean; /** Append a deduplicated link list, populating {@link ReadResponse.links}. */
|
|
20
|
+
readonly withLinksSummary?: boolean;
|
|
21
|
+
};
|
|
22
|
+
type JinaReaderService = {
|
|
23
|
+
readonly read: (request: JinaReadRequest) => Effect.Effect<ReadResponse, AiError.AiError>;
|
|
24
|
+
};
|
|
25
|
+
declare const JinaReader_base: Context.ServiceClass<JinaReader, "@betalyra/effect-uai/providers/jina/JinaReader", JinaReaderService>;
|
|
26
|
+
/**
|
|
27
|
+
* Provider-typed service tag. Yield this for Jina-typed reads (its own
|
|
28
|
+
* Reader headers); yield the generic `WebRead` tag for provider-portable
|
|
29
|
+
* code. Both are registered by {@link layer}.
|
|
30
|
+
*/
|
|
31
|
+
declare class JinaReader extends JinaReader_base {}
|
|
32
|
+
type Config = {
|
|
33
|
+
readonly apiKey: Redacted.Redacted;
|
|
34
|
+
readonly baseUrl?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Build a `JinaReaderService` value. For Layer-based setup, prefer
|
|
38
|
+
* {@link layer}.
|
|
39
|
+
*/
|
|
40
|
+
declare const make: (cfg: Config) => Effect.Effect<JinaReaderService, never, HttpClient.HttpClient>;
|
|
41
|
+
/**
|
|
42
|
+
* Layer registering both the provider-typed `JinaReader` tag and the generic
|
|
43
|
+
* `WebRead` tag over one implementation. A `CommonReadRequest` is
|
|
44
|
+
* structurally a `JinaReadRequest` with the extra knobs unset, so the generic
|
|
45
|
+
* registration forwards directly.
|
|
46
|
+
*/
|
|
47
|
+
declare const layer: (cfg: Config) => Layer.Layer<JinaReader | WebRead, never, HttpClient.HttpClient>;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { Config, JinaReadRequest, JinaReader, JinaReaderService, layer, make, JinaReader_d_exports as t };
|
|
50
|
+
//# sourceMappingURL=JinaReader.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JinaReader.d.mts","names":[],"sources":["../src/JinaReader.ts"],"mappings":";;;;;;;;;;;;;;;KAoBY,eAAA,GAAkB,iBAAA;2DAEnB,MAAA,GAAS,UAAA;WAET,cAAA;WAEA,OAAA,YANgB;EAAA,SAQhB,gBAAA;AAAA;AAAA,KAGC,iBAAA;EAAA,SACD,IAAA,GAAO,OAAA,EAAS,eAAA,KAAoB,MAAA,CAAO,MAAA,CAAO,YAAA,EAAc,OAAA,CAAQ,OAAA;AAAA;AAAA,cAClF,eAAA;;;;;AAFD;cASa,UAAA,SAAmB,eAAA;AAAA,KAIpB,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;cAkIE,IAAA,GAAQ,GAAA,EAAK,MAAA,KAAS,MAAA,CAAO,MAAA,CAAO,iBAAA,SAA0B,UAAA,CAAW,UAAA;;;;;;;cAYzE,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,UAAA,GAAa,OAAA,SAAgB,UAAA,CAAW,UAAA"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
|
+
import { Context, Duration, Effect, Layer, Schema } from "effect";
|
|
3
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
4
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
5
|
+
import { WebRead } from "@effect-uai/core/WebRead";
|
|
6
|
+
//#region src/JinaReader.ts
|
|
7
|
+
var JinaReader_exports = /* @__PURE__ */ __exportAll({
|
|
8
|
+
JinaReader: () => JinaReader,
|
|
9
|
+
layer: () => layer,
|
|
10
|
+
make: () => make
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Provider-typed service tag. Yield this for Jina-typed reads (its own
|
|
14
|
+
* Reader headers); yield the generic `WebRead` tag for provider-portable
|
|
15
|
+
* code. Both are registered by {@link layer}.
|
|
16
|
+
*/
|
|
17
|
+
var JinaReader = class extends Context.Service()("@betalyra/effect-uai/providers/jina/JinaReader") {};
|
|
18
|
+
const buildHeaders = (request) => {
|
|
19
|
+
return {
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
"X-Return-Format": request.format ?? "markdown",
|
|
22
|
+
...request.timeout !== void 0 && { "X-Timeout": String(Math.max(1, Math.round(Duration.toMillis(request.timeout) / 1e3))) },
|
|
23
|
+
...request.engine !== void 0 && { "X-Engine": request.engine },
|
|
24
|
+
...request.targetSelector !== void 0 && { "X-Target-Selector": request.targetSelector },
|
|
25
|
+
...request.noCache === true && { "X-No-Cache": "true" },
|
|
26
|
+
...request.withLinksSummary === true && { "X-With-Links-Summary": "true" }
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const WireData = Schema.Struct({
|
|
30
|
+
title: Schema.optional(Schema.NullOr(Schema.String)),
|
|
31
|
+
url: Schema.optional(Schema.NullOr(Schema.String)),
|
|
32
|
+
content: Schema.optional(Schema.NullOr(Schema.String)),
|
|
33
|
+
html: Schema.optional(Schema.NullOr(Schema.String)),
|
|
34
|
+
links: Schema.optional(Schema.Unknown)
|
|
35
|
+
});
|
|
36
|
+
const WireResponse = Schema.Struct({
|
|
37
|
+
code: Schema.optional(Schema.Number),
|
|
38
|
+
status: Schema.optional(Schema.Number),
|
|
39
|
+
data: WireData
|
|
40
|
+
});
|
|
41
|
+
const extractLinks = (links) => {
|
|
42
|
+
const urls = (Array.isArray(links) ? links : links !== null && typeof links === "object" ? Object.values(links) : []).filter((v) => typeof v === "string");
|
|
43
|
+
return urls.length > 0 ? urls : void 0;
|
|
44
|
+
};
|
|
45
|
+
const toResponse = (wire, request) => {
|
|
46
|
+
const { data } = wire;
|
|
47
|
+
const content = ((request.format ?? "markdown") === "html" ? data.html : data.content) ?? "";
|
|
48
|
+
const title = data.title ?? void 0;
|
|
49
|
+
const links = extractLinks(data.links);
|
|
50
|
+
return {
|
|
51
|
+
url: data.url ?? request.url,
|
|
52
|
+
content,
|
|
53
|
+
...title !== void 0 && { title },
|
|
54
|
+
...links !== void 0 && { links },
|
|
55
|
+
raw: wire
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const transportFailure = (cause) => new AiError.Unavailable({
|
|
59
|
+
provider: "jina",
|
|
60
|
+
raw: cause
|
|
61
|
+
});
|
|
62
|
+
const httpStatusError = (status, body) => {
|
|
63
|
+
const provider = "jina";
|
|
64
|
+
const raw = body;
|
|
65
|
+
if (status === 429) return new AiError.RateLimited({
|
|
66
|
+
provider,
|
|
67
|
+
raw
|
|
68
|
+
});
|
|
69
|
+
if (status === 408 || status === 504) return new AiError.Timeout({
|
|
70
|
+
provider,
|
|
71
|
+
raw
|
|
72
|
+
});
|
|
73
|
+
if (status === 401) return new AiError.AuthFailed({
|
|
74
|
+
provider,
|
|
75
|
+
subtype: "auth",
|
|
76
|
+
raw
|
|
77
|
+
});
|
|
78
|
+
if (status === 403) return new AiError.AuthFailed({
|
|
79
|
+
provider,
|
|
80
|
+
subtype: "permission",
|
|
81
|
+
raw
|
|
82
|
+
});
|
|
83
|
+
if (status === 402) return new AiError.AuthFailed({
|
|
84
|
+
provider,
|
|
85
|
+
subtype: "billing",
|
|
86
|
+
raw
|
|
87
|
+
});
|
|
88
|
+
if (status >= 500) return new AiError.Unavailable({
|
|
89
|
+
provider,
|
|
90
|
+
status,
|
|
91
|
+
raw
|
|
92
|
+
});
|
|
93
|
+
return new AiError.InvalidRequest({
|
|
94
|
+
provider,
|
|
95
|
+
raw
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const baseUrl = (cfg) => cfg.baseUrl ?? "https://r.jina.ai";
|
|
99
|
+
const getRead = (cfg, request) => Effect.gen(function* () {
|
|
100
|
+
const client = yield* HttpClient.HttpClient;
|
|
101
|
+
const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/${request.url}`).pipe(HttpClientRequest.bearerToken(cfg.apiKey), HttpClientRequest.setHeaders(buildHeaders(request)));
|
|
102
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
|
|
103
|
+
if (response.status >= 400) {
|
|
104
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
105
|
+
return yield* httpStatusError(response.status, text);
|
|
106
|
+
}
|
|
107
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure));
|
|
108
|
+
return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(Effect.mapError(transportFailure));
|
|
109
|
+
});
|
|
110
|
+
const readImpl = (cfg) => (request) => Effect.map(getRead(cfg, request), (wire) => toResponse(wire, request));
|
|
111
|
+
/**
|
|
112
|
+
* Build a `JinaReaderService` value. For Layer-based setup, prefer
|
|
113
|
+
* {@link layer}.
|
|
114
|
+
*/
|
|
115
|
+
const make = (cfg) => Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({ read: (request) => readImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)) }));
|
|
116
|
+
/**
|
|
117
|
+
* Layer registering both the provider-typed `JinaReader` tag and the generic
|
|
118
|
+
* `WebRead` tag over one implementation. A `CommonReadRequest` is
|
|
119
|
+
* structurally a `JinaReadRequest` with the extra knobs unset, so the generic
|
|
120
|
+
* registration forwards directly.
|
|
121
|
+
*/
|
|
122
|
+
const layer = (cfg) => {
|
|
123
|
+
const typed = Layer.effect(JinaReader, make(cfg));
|
|
124
|
+
const generic = Layer.effect(WebRead, Effect.map(make(cfg), (s) => ({ read: (request) => s.read(request) })));
|
|
125
|
+
return Layer.merge(typed, generic);
|
|
126
|
+
};
|
|
127
|
+
//#endregion
|
|
128
|
+
export { JinaReader, layer, make, JinaReader_exports as t };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=JinaReader.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JinaReader.mjs","names":[],"sources":["../src/JinaReader.ts"],"sourcesContent":["import { Context, Duration, Effect, Layer, type Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonReadRequest,\n type ReadResponse,\n WebRead,\n type WebReadService,\n} from \"@effect-uai/core/WebRead\"\nimport type { JinaEngine } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Jina-typed read request. Widens {@link CommonReadRequest} with Jina\n * Reader's own header knobs; the common fields map onto Reader headers (see\n * the codec below).\n */\nexport type JinaReadRequest = CommonReadRequest & {\n /** Fetching engine - see {@link JinaEngine}. */\n readonly engine?: JinaEngine\n /** CSS selector to extract instead of the whole page (`X-Target-Selector`). */\n readonly targetSelector?: string\n /** Bypass Jina's page cache (`X-No-Cache`). */\n readonly noCache?: boolean\n /** Append a deduplicated link list, populating {@link ReadResponse.links}. */\n readonly withLinksSummary?: boolean\n}\n\nexport type JinaReaderService = {\n readonly read: (request: JinaReadRequest) => Effect.Effect<ReadResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this for Jina-typed reads (its own\n * Reader headers); yield the generic `WebRead` tag for provider-portable\n * code. Both are registered by {@link layer}.\n */\nexport class JinaReader extends Context.Service<JinaReader, JinaReaderService>()(\n \"@betalyra/effect-uai/providers/jina/JinaReader\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request headers\n// ---------------------------------------------------------------------------\n\n// Reader is header-driven: the URL is the path, everything else is a header.\n// `markdown` / `html` map one-to-one onto `X-Return-Format`.\nconst buildHeaders = (request: JinaReadRequest): Record<string, string> => {\n const format = request.format ?? \"markdown\"\n return {\n Accept: \"application/json\",\n \"X-Return-Format\": format,\n ...(request.timeout !== undefined && {\n // `X-Timeout` is whole seconds, capped at 180 by Jina.\n \"X-Timeout\": String(Math.max(1, Math.round(Duration.toMillis(request.timeout) / 1000))),\n }),\n ...(request.engine !== undefined && { \"X-Engine\": request.engine }),\n ...(request.targetSelector !== undefined && { \"X-Target-Selector\": request.targetSelector }),\n ...(request.noCache === true && { \"X-No-Cache\": \"true\" }),\n ...(request.withLinksSummary === true && { \"X-With-Links-Summary\": \"true\" }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireData = Schema.Struct({\n title: Schema.optional(Schema.NullOr(Schema.String)),\n url: Schema.optional(Schema.NullOr(Schema.String)),\n // `content` carries markdown/text; `html` carries html. Exactly one is set,\n // depending on the requested format.\n content: Schema.optional(Schema.NullOr(Schema.String)),\n html: Schema.optional(Schema.NullOr(Schema.String)),\n // Present only with `X-With-Links-Summary`; a { anchorText: url } record.\n links: Schema.optional(Schema.Unknown),\n})\n\nconst WireResponse = Schema.Struct({\n code: Schema.optional(Schema.Number),\n status: Schema.optional(Schema.Number),\n data: WireData,\n})\ntype WireResponse = typeof WireResponse.Type\n\n// Jina reports links as a { text: url } record; take the URLs. Tolerate an\n// array shape too, and omit when empty.\nconst extractLinks = (links: unknown): ReadonlyArray<string> | undefined => {\n const values = Array.isArray(links)\n ? links\n : links !== null && typeof links === \"object\"\n ? Object.values(links as Record<string, unknown>)\n : []\n const urls = values.filter((v): v is string => typeof v === \"string\")\n return urls.length > 0 ? urls : undefined\n}\n\nconst toResponse = (wire: WireResponse, request: JinaReadRequest): ReadResponse => {\n const { data } = wire\n const format = request.format ?? \"markdown\"\n const content = (format === \"html\" ? data.html : data.content) ?? \"\"\n const title = data.title ?? undefined\n const links = extractLinks(data.links)\n return {\n url: data.url ?? request.url,\n content,\n ...(title !== undefined && { title }),\n ...(links !== undefined && { links }),\n raw: wire,\n }\n}\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"jina\", raw: cause })\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"jina\"\n const raw = body\n if (status === 429) return new AiError.RateLimited({ provider, raw })\n if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })\n if (status === 401) return new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n if (status === 403) return new AiError.AuthFailed({ provider, subtype: \"permission\", raw })\n if (status === 402) return new AiError.AuthFailed({ provider, subtype: \"billing\", raw })\n if (status >= 500) return new AiError.Unavailable({ provider, status, raw })\n return new AiError.InvalidRequest({ provider, raw })\n}\n\nconst baseUrl = (cfg: Config): string => cfg.baseUrl ?? \"https://r.jina.ai\"\n\nconst getRead = (\n cfg: Config,\n request: JinaReadRequest,\n): Effect.Effect<WireResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n // The target URL is appended as the path: `https://r.jina.ai/<url>`.\n const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/${request.url}`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.setHeaders(buildHeaders(request)),\n )\n const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return yield* httpStatusError(response.status, text)\n }\n const json = yield* response.json.pipe(Effect.mapError(transportFailure))\n return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(\n Effect.mapError(transportFailure),\n )\n })\n\nconst readImpl =\n (cfg: Config) =>\n (request: JinaReadRequest): Effect.Effect<ReadResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.map(getRead(cfg, request), (wire) => toResponse(wire, request))\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `JinaReaderService` value. For Layer-based setup, prefer\n * {@link layer}.\n */\nexport const make = (cfg: Config): Effect.Effect<JinaReaderService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({\n read: (request) =>\n readImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer registering both the provider-typed `JinaReader` tag and the generic\n * `WebRead` tag over one implementation. A `CommonReadRequest` is\n * structurally a `JinaReadRequest` with the extra knobs unset, so the generic\n * registration forwards directly.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<JinaReader | WebRead, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(JinaReader, make(cfg))\n const generic = Layer.effect(\n WebRead,\n Effect.map(make(cfg), (s): WebReadService => ({ read: (request) => s.read(request) })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { JinaEmbeddingModel } from "./models.mjs";
|
|
1
|
+
import { JinaEmbeddingModel, JinaEngine } from "./models.mjs";
|
|
2
2
|
import { t as JinaEmbedding_d_exports } from "./JinaEmbedding.mjs";
|
|
3
|
-
|
|
3
|
+
import { t as JinaReader_d_exports } from "./JinaReader.mjs";
|
|
4
|
+
export { JinaEmbedding_d_exports as JinaEmbedding, JinaEmbeddingModel, JinaEngine, JinaReader_d_exports as JinaReader };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { t as JinaEmbedding_exports } from "./JinaEmbedding.mjs";
|
|
2
|
+
import { t as JinaReader_exports } from "./JinaReader.mjs";
|
|
2
3
|
import "./models.mjs";
|
|
3
|
-
export { JinaEmbedding_exports as JinaEmbedding };
|
|
4
|
+
export { JinaEmbedding_exports as JinaEmbedding, JinaReader_exports as JinaReader };
|
package/dist/models.d.mts
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
* Reference: https://jina.ai/embeddings/
|
|
20
20
|
*/
|
|
21
21
|
type JinaEmbeddingModel = "jina-embeddings-v4" | "jina-embeddings-v5-text-small" | "jina-embeddings-v5-text-nano" | "jina-embeddings-v3" | "jina-clip-v2" | (string & {});
|
|
22
|
+
/**
|
|
23
|
+
* Reader fetching engine (`X-Engine`). `browser` renders JavaScript,
|
|
24
|
+
* `curl` is a fast static fetch, `auto` picks per page. Provider-specific,
|
|
25
|
+
* so it lives on `JinaReadRequest`.
|
|
26
|
+
*/
|
|
27
|
+
type JinaEngine = "browser" | "curl" | "auto" | (string & {});
|
|
22
28
|
//#endregion
|
|
23
|
-
export { JinaEmbeddingModel };
|
|
29
|
+
export { JinaEmbeddingModel, JinaEngine };
|
|
24
30
|
//# sourceMappingURL=models.d.mts.map
|
package/dist/models.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;AAmBA
|
|
1
|
+
{"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;AAmBA;;;;;AAcA;;;;;;;;;;;;;KAdY,kBAAA;;;;;;KAcA,UAAA"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/jina",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Jina AI provider for @effect-uai/core (embeddings, rerank).",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Jina AI provider for @effect-uai/core (embeddings, rerank, web read).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
7
7
|
"effect",
|
|
8
8
|
"embeddings",
|
|
9
9
|
"jina",
|
|
10
|
-
"rerank"
|
|
10
|
+
"rerank",
|
|
11
|
+
"web-read"
|
|
11
12
|
],
|
|
12
13
|
"homepage": "https://github.com/betalyra/effect-uai/tree/main/packages/providers/jina",
|
|
13
14
|
"bugs": {
|
|
@@ -37,6 +38,10 @@
|
|
|
37
38
|
"./JinaEmbedding": {
|
|
38
39
|
"types": "./dist/JinaEmbedding.d.mts",
|
|
39
40
|
"import": "./dist/JinaEmbedding.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./JinaReader": {
|
|
43
|
+
"types": "./dist/JinaReader.d.mts",
|
|
44
|
+
"import": "./dist/JinaReader.mjs"
|
|
40
45
|
}
|
|
41
46
|
},
|
|
42
47
|
"publishConfig": {
|
|
@@ -45,7 +50,7 @@
|
|
|
45
50
|
"devDependencies": {
|
|
46
51
|
"effect": "4.0.0-beta.57",
|
|
47
52
|
"typescript": "^6.0.3",
|
|
48
|
-
"@effect-uai/core": "0.
|
|
53
|
+
"@effect-uai/core": "0.10.0"
|
|
49
54
|
},
|
|
50
55
|
"peerDependencies": {
|
|
51
56
|
"@effect-uai/core": ">=0.2.0 <1",
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Context, Duration, Effect, Layer, type Redacted, Schema } from "effect"
|
|
2
|
+
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
3
|
+
import * as AiError from "@effect-uai/core/AiError"
|
|
4
|
+
import {
|
|
5
|
+
type CommonReadRequest,
|
|
6
|
+
type ReadResponse,
|
|
7
|
+
WebRead,
|
|
8
|
+
type WebReadService,
|
|
9
|
+
} from "@effect-uai/core/WebRead"
|
|
10
|
+
import type { JinaEngine } from "./models.js"
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Public types
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Jina-typed read request. Widens {@link CommonReadRequest} with Jina
|
|
18
|
+
* Reader's own header knobs; the common fields map onto Reader headers (see
|
|
19
|
+
* the codec below).
|
|
20
|
+
*/
|
|
21
|
+
export type JinaReadRequest = CommonReadRequest & {
|
|
22
|
+
/** Fetching engine - see {@link JinaEngine}. */
|
|
23
|
+
readonly engine?: JinaEngine
|
|
24
|
+
/** CSS selector to extract instead of the whole page (`X-Target-Selector`). */
|
|
25
|
+
readonly targetSelector?: string
|
|
26
|
+
/** Bypass Jina's page cache (`X-No-Cache`). */
|
|
27
|
+
readonly noCache?: boolean
|
|
28
|
+
/** Append a deduplicated link list, populating {@link ReadResponse.links}. */
|
|
29
|
+
readonly withLinksSummary?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type JinaReaderService = {
|
|
33
|
+
readonly read: (request: JinaReadRequest) => Effect.Effect<ReadResponse, AiError.AiError>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Provider-typed service tag. Yield this for Jina-typed reads (its own
|
|
38
|
+
* Reader headers); yield the generic `WebRead` tag for provider-portable
|
|
39
|
+
* code. Both are registered by {@link layer}.
|
|
40
|
+
*/
|
|
41
|
+
export class JinaReader extends Context.Service<JinaReader, JinaReaderService>()(
|
|
42
|
+
"@betalyra/effect-uai/providers/jina/JinaReader",
|
|
43
|
+
) {}
|
|
44
|
+
|
|
45
|
+
export type Config = {
|
|
46
|
+
readonly apiKey: Redacted.Redacted
|
|
47
|
+
readonly baseUrl?: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Codec - request headers
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
// Reader is header-driven: the URL is the path, everything else is a header.
|
|
55
|
+
// `markdown` / `html` map one-to-one onto `X-Return-Format`.
|
|
56
|
+
const buildHeaders = (request: JinaReadRequest): Record<string, string> => {
|
|
57
|
+
const format = request.format ?? "markdown"
|
|
58
|
+
return {
|
|
59
|
+
Accept: "application/json",
|
|
60
|
+
"X-Return-Format": format,
|
|
61
|
+
...(request.timeout !== undefined && {
|
|
62
|
+
// `X-Timeout` is whole seconds, capped at 180 by Jina.
|
|
63
|
+
"X-Timeout": String(Math.max(1, Math.round(Duration.toMillis(request.timeout) / 1000))),
|
|
64
|
+
}),
|
|
65
|
+
...(request.engine !== undefined && { "X-Engine": request.engine }),
|
|
66
|
+
...(request.targetSelector !== undefined && { "X-Target-Selector": request.targetSelector }),
|
|
67
|
+
...(request.noCache === true && { "X-No-Cache": "true" }),
|
|
68
|
+
...(request.withLinksSummary === true && { "X-With-Links-Summary": "true" }),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Codec - response
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
const WireData = Schema.Struct({
|
|
77
|
+
title: Schema.optional(Schema.NullOr(Schema.String)),
|
|
78
|
+
url: Schema.optional(Schema.NullOr(Schema.String)),
|
|
79
|
+
// `content` carries markdown/text; `html` carries html. Exactly one is set,
|
|
80
|
+
// depending on the requested format.
|
|
81
|
+
content: Schema.optional(Schema.NullOr(Schema.String)),
|
|
82
|
+
html: Schema.optional(Schema.NullOr(Schema.String)),
|
|
83
|
+
// Present only with `X-With-Links-Summary`; a { anchorText: url } record.
|
|
84
|
+
links: Schema.optional(Schema.Unknown),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const WireResponse = Schema.Struct({
|
|
88
|
+
code: Schema.optional(Schema.Number),
|
|
89
|
+
status: Schema.optional(Schema.Number),
|
|
90
|
+
data: WireData,
|
|
91
|
+
})
|
|
92
|
+
type WireResponse = typeof WireResponse.Type
|
|
93
|
+
|
|
94
|
+
// Jina reports links as a { text: url } record; take the URLs. Tolerate an
|
|
95
|
+
// array shape too, and omit when empty.
|
|
96
|
+
const extractLinks = (links: unknown): ReadonlyArray<string> | undefined => {
|
|
97
|
+
const values = Array.isArray(links)
|
|
98
|
+
? links
|
|
99
|
+
: links !== null && typeof links === "object"
|
|
100
|
+
? Object.values(links as Record<string, unknown>)
|
|
101
|
+
: []
|
|
102
|
+
const urls = values.filter((v): v is string => typeof v === "string")
|
|
103
|
+
return urls.length > 0 ? urls : undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const toResponse = (wire: WireResponse, request: JinaReadRequest): ReadResponse => {
|
|
107
|
+
const { data } = wire
|
|
108
|
+
const format = request.format ?? "markdown"
|
|
109
|
+
const content = (format === "html" ? data.html : data.content) ?? ""
|
|
110
|
+
const title = data.title ?? undefined
|
|
111
|
+
const links = extractLinks(data.links)
|
|
112
|
+
return {
|
|
113
|
+
url: data.url ?? request.url,
|
|
114
|
+
content,
|
|
115
|
+
...(title !== undefined && { title }),
|
|
116
|
+
...(links !== undefined && { links }),
|
|
117
|
+
raw: wire,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// HTTP
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
const transportFailure = (cause: unknown): AiError.AiError =>
|
|
126
|
+
new AiError.Unavailable({ provider: "jina", raw: cause })
|
|
127
|
+
|
|
128
|
+
const httpStatusError = (status: number, body: string): AiError.AiError => {
|
|
129
|
+
const provider = "jina"
|
|
130
|
+
const raw = body
|
|
131
|
+
if (status === 429) return new AiError.RateLimited({ provider, raw })
|
|
132
|
+
if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })
|
|
133
|
+
if (status === 401) return new AiError.AuthFailed({ provider, subtype: "auth", raw })
|
|
134
|
+
if (status === 403) return new AiError.AuthFailed({ provider, subtype: "permission", raw })
|
|
135
|
+
if (status === 402) return new AiError.AuthFailed({ provider, subtype: "billing", raw })
|
|
136
|
+
if (status >= 500) return new AiError.Unavailable({ provider, status, raw })
|
|
137
|
+
return new AiError.InvalidRequest({ provider, raw })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const baseUrl = (cfg: Config): string => cfg.baseUrl ?? "https://r.jina.ai"
|
|
141
|
+
|
|
142
|
+
const getRead = (
|
|
143
|
+
cfg: Config,
|
|
144
|
+
request: JinaReadRequest,
|
|
145
|
+
): Effect.Effect<WireResponse, AiError.AiError, HttpClient.HttpClient> =>
|
|
146
|
+
Effect.gen(function* () {
|
|
147
|
+
const client = yield* HttpClient.HttpClient
|
|
148
|
+
// The target URL is appended as the path: `https://r.jina.ai/<url>`.
|
|
149
|
+
const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/${request.url}`).pipe(
|
|
150
|
+
HttpClientRequest.bearerToken(cfg.apiKey),
|
|
151
|
+
HttpClientRequest.setHeaders(buildHeaders(request)),
|
|
152
|
+
)
|
|
153
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))
|
|
154
|
+
if (response.status >= 400) {
|
|
155
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""))
|
|
156
|
+
return yield* httpStatusError(response.status, text)
|
|
157
|
+
}
|
|
158
|
+
const json = yield* response.json.pipe(Effect.mapError(transportFailure))
|
|
159
|
+
return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(
|
|
160
|
+
Effect.mapError(transportFailure),
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const readImpl =
|
|
165
|
+
(cfg: Config) =>
|
|
166
|
+
(request: JinaReadRequest): Effect.Effect<ReadResponse, AiError.AiError, HttpClient.HttpClient> =>
|
|
167
|
+
Effect.map(getRead(cfg, request), (wire) => toResponse(wire, request))
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Constructors
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Build a `JinaReaderService` value. For Layer-based setup, prefer
|
|
175
|
+
* {@link layer}.
|
|
176
|
+
*/
|
|
177
|
+
export const make = (cfg: Config): Effect.Effect<JinaReaderService, never, HttpClient.HttpClient> =>
|
|
178
|
+
Effect.map(HttpClient.HttpClient.asEffect(), (client) => ({
|
|
179
|
+
read: (request) =>
|
|
180
|
+
readImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
|
|
181
|
+
}))
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Layer registering both the provider-typed `JinaReader` tag and the generic
|
|
185
|
+
* `WebRead` tag over one implementation. A `CommonReadRequest` is
|
|
186
|
+
* structurally a `JinaReadRequest` with the extra knobs unset, so the generic
|
|
187
|
+
* registration forwards directly.
|
|
188
|
+
*/
|
|
189
|
+
export const layer = (
|
|
190
|
+
cfg: Config,
|
|
191
|
+
): Layer.Layer<JinaReader | WebRead, never, HttpClient.HttpClient> => {
|
|
192
|
+
const typed = Layer.effect(JinaReader, make(cfg))
|
|
193
|
+
const generic = Layer.effect(
|
|
194
|
+
WebRead,
|
|
195
|
+
Effect.map(make(cfg), (s): WebReadService => ({ read: (request) => s.read(request) })),
|
|
196
|
+
)
|
|
197
|
+
return Layer.merge(typed, generic)
|
|
198
|
+
}
|
package/src/index.ts
CHANGED
package/src/models.ts
CHANGED
|
@@ -25,3 +25,15 @@ export type JinaEmbeddingModel =
|
|
|
25
25
|
| "jina-clip-v2"
|
|
26
26
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
27
27
|
| (string & {})
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reader fetching engine (`X-Engine`). `browser` renders JavaScript,
|
|
31
|
+
* `curl` is a fast static fetch, `auto` picks per page. Provider-specific,
|
|
32
|
+
* so it lives on `JinaReadRequest`.
|
|
33
|
+
*/
|
|
34
|
+
export type JinaEngine =
|
|
35
|
+
| "browser"
|
|
36
|
+
| "curl"
|
|
37
|
+
| "auto"
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
39
|
+
| (string & {})
|