@effect-uai/jina 0.12.1 → 0.14.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.
@@ -0,0 +1,53 @@
1
+ import { JinaRerankerModel } 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 { CommonRerankRequest, RerankResponse, Reranker } from "@effect-uai/core/Reranker";
6
+ import { ImageSource } from "@effect-uai/core/Image";
7
+ declare namespace JinaReranker_d_exports {
8
+ export { Config, JinaRerankDocument, JinaRerankRequest, JinaReranker, JinaRerankerService, layer, make };
9
+ }
10
+ /**
11
+ * A candidate document. Text models take strings; `jina-reranker-m0` also
12
+ * takes `{ text }` / `{ image }`. One entry is one modality, and image
13
+ * *queries* are local-inference only, so `query` stays a string.
14
+ */
15
+ type JinaRerankDocument = string | {
16
+ readonly text: string;
17
+ } | {
18
+ readonly image: ImageSource;
19
+ };
20
+ /** Widens {@link CommonRerankRequest} with Jina's multimodal documents. */
21
+ type JinaRerankRequest = Omit<CommonRerankRequest, "model" | "documents"> & {
22
+ readonly model: JinaRerankerModel;
23
+ readonly documents: ReadonlyArray<JinaRerankDocument>;
24
+ };
25
+ type JinaRerankerService = {
26
+ readonly rerank: (request: JinaRerankRequest) => Effect.Effect<RerankResponse, AiError.AiError>;
27
+ };
28
+ declare const JinaReranker_base: Context.ServiceClass<JinaReranker, "@betalyra/effect-uai/providers/jina/JinaReranker", JinaRerankerService>;
29
+ /**
30
+ * Provider-typed service tag. Yield this for multimodal documents; yield the
31
+ * generic `Reranker` tag for provider-portable code. Both are registered by
32
+ * {@link layer}.
33
+ */
34
+ declare class JinaReranker extends JinaReranker_base {}
35
+ type Config = {
36
+ readonly apiKey: Redacted.Redacted;
37
+ readonly baseUrl?: string;
38
+ };
39
+ /**
40
+ * Build a `JinaRerankerService` value. For Layer-based setup, prefer
41
+ * {@link layer}.
42
+ */
43
+ declare const make: (cfg: Config) => Effect.Effect<JinaRerankerService, never, HttpClient.HttpClient>;
44
+ /**
45
+ * Layer registering both the provider-typed `JinaReranker` tag and the generic
46
+ * `Reranker` tag over one implementation. A `CommonRerankRequest` is
47
+ * structurally a `JinaRerankRequest` with string documents, so the generic
48
+ * registration forwards directly.
49
+ */
50
+ declare const layer: (cfg: Config) => Layer.Layer<JinaReranker | Reranker, never, HttpClient.HttpClient>;
51
+ //#endregion
52
+ export { Config, JinaRerankDocument, JinaRerankRequest, JinaReranker, JinaRerankerService, layer, make, JinaReranker_d_exports as t };
53
+ //# sourceMappingURL=JinaReranker.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JinaReranker.d.mts","names":[],"sources":["../src/JinaReranker.ts"],"mappings":";;;;;;;;;;;;;;KAsBY;WAEG;;WACA,OAAO;;;KAGV,oBAAoB,KAAK;WAC1B,OAAO;WACP,WAAW,cAAc;;KAGxB;WACD,SAAS,SAAS,sBAAsB,OAAO,OAAO,gBAAgB,QAAQ;;;;;;;;cAQ5E,qBAAqB;KAItB;WACD,QAAQ,SAAS;WACjB;;;;;;cAmIE,OAAI,KACV,WACJ,OAAO,OAAO,4BAA4B,WAAW;;;;;;;cAY3C,QAAK,KACX,WACJ,MAAM,MAAM,eAAe,iBAAiB,WAAW"}
@@ -0,0 +1,120 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { Context, Effect, Encoding, Layer, Match, Schema } from "effect";
3
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
+ import * as AiError from "@effect-uai/core/AiError";
5
+ import { Reranker } from "@effect-uai/core/Reranker";
6
+ //#region src/JinaReranker.ts
7
+ var JinaReranker_exports = /* @__PURE__ */ __exportAll({
8
+ JinaReranker: () => JinaReranker,
9
+ layer: () => layer,
10
+ make: () => make
11
+ });
12
+ /**
13
+ * Provider-typed service tag. Yield this for multimodal documents; yield the
14
+ * generic `Reranker` tag for provider-portable code. Both are registered by
15
+ * {@link layer}.
16
+ */
17
+ var JinaReranker = class extends Context.Service()("@betalyra/effect-uai/providers/jina/JinaReranker") {};
18
+ const imageSourceToWire = Match.type().pipe(Match.tag("url", (s) => ({ image: s.url })), Match.tag("base64", (s) => ({ image: s.base64 })), Match.tag("bytes", (s) => ({ image: Encoding.encodeBase64(s.bytes) })), Match.exhaustive);
19
+ const documentToWire = Match.type().pipe(Match.when(Match.string, (s) => s), Match.when({ text: Match.string }, ({ text }) => ({ text })), Match.when({ image: Match.any }, ({ image }) => imageSourceToWire(image)), Match.exhaustive);
20
+ const buildBody = (request) => ({
21
+ model: request.model,
22
+ query: request.query,
23
+ documents: request.documents.map(documentToWire),
24
+ ...request.topN !== void 0 && { top_n: request.topN }
25
+ });
26
+ const WireResult = Schema.Struct({
27
+ index: Schema.Number,
28
+ relevance_score: Schema.Number
29
+ });
30
+ const WireUsage = Schema.Struct({ total_tokens: Schema.optional(Schema.Number) });
31
+ const WireResponse = Schema.Struct({
32
+ model: Schema.optional(Schema.String),
33
+ usage: Schema.optional(WireUsage),
34
+ results: Schema.Array(WireResult)
35
+ });
36
+ const usageOf = (usage) => usage?.total_tokens !== void 0 ? { totalTokens: usage.total_tokens } : {};
37
+ const toResponse = (wire) => ({
38
+ results: [...wire.results].sort((a, b) => b.relevance_score - a.relevance_score).map((r) => ({
39
+ index: r.index,
40
+ score: r.relevance_score
41
+ })),
42
+ usage: usageOf(wire.usage)
43
+ });
44
+ const transportFailure = (cause) => new AiError.Unavailable({
45
+ provider: "jina",
46
+ raw: cause
47
+ });
48
+ const httpStatusError = (status, body) => {
49
+ const provider = "jina";
50
+ const raw = body;
51
+ if (status === 429) return new AiError.RateLimited({
52
+ provider,
53
+ raw
54
+ });
55
+ if (status === 408 || status === 504) return new AiError.Timeout({
56
+ provider,
57
+ raw
58
+ });
59
+ if (status === 401) return new AiError.AuthFailed({
60
+ provider,
61
+ subtype: "auth",
62
+ raw
63
+ });
64
+ if (status === 403) return new AiError.AuthFailed({
65
+ provider,
66
+ subtype: "permission",
67
+ raw
68
+ });
69
+ if (status === 402) return new AiError.AuthFailed({
70
+ provider,
71
+ subtype: "billing",
72
+ raw
73
+ });
74
+ if (status === 413) return new AiError.ContextLengthExceeded({
75
+ provider,
76
+ raw
77
+ });
78
+ if (status >= 500) return new AiError.Unavailable({
79
+ provider,
80
+ status,
81
+ raw
82
+ });
83
+ return new AiError.InvalidRequest({
84
+ provider,
85
+ raw
86
+ });
87
+ };
88
+ const baseUrl = (cfg) => cfg.baseUrl ?? "https://api.jina.ai/v1";
89
+ const postRerank = (cfg, body) => Effect.gen(function* () {
90
+ const client = yield* HttpClient.HttpClient;
91
+ const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/rerank`).pipe(HttpClientRequest.bearerToken(cfg.apiKey), HttpClientRequest.bodyJsonUnsafe(body));
92
+ const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure));
93
+ if (response.status >= 400) {
94
+ const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
95
+ return yield* httpStatusError(response.status, text);
96
+ }
97
+ const json = yield* response.json.pipe(Effect.mapError(transportFailure));
98
+ return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(Effect.mapError(transportFailure));
99
+ });
100
+ const rerankImpl = (cfg) => (request) => Effect.map(postRerank(cfg, buildBody(request)), toResponse);
101
+ /**
102
+ * Build a `JinaRerankerService` value. For Layer-based setup, prefer
103
+ * {@link layer}.
104
+ */
105
+ const make = (cfg) => Effect.map(HttpClient.HttpClient, (client) => ({ rerank: (request) => rerankImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)) }));
106
+ /**
107
+ * Layer registering both the provider-typed `JinaReranker` tag and the generic
108
+ * `Reranker` tag over one implementation. A `CommonRerankRequest` is
109
+ * structurally a `JinaRerankRequest` with string documents, so the generic
110
+ * registration forwards directly.
111
+ */
112
+ const layer = (cfg) => {
113
+ const typed = Layer.effect(JinaReranker, make(cfg));
114
+ const generic = Layer.effect(Reranker, Effect.map(make(cfg), (s) => ({ rerank: (request) => s.rerank(request) })));
115
+ return Layer.merge(typed, generic);
116
+ };
117
+ //#endregion
118
+ export { JinaReranker, layer, make, JinaReranker_exports as t };
119
+
120
+ //# sourceMappingURL=JinaReranker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JinaReranker.mjs","names":[],"sources":["../src/JinaReranker.ts"],"sourcesContent":["import { Context, Effect, Encoding, Layer, Match, type Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { ImageSource } from \"@effect-uai/core/Image\"\nimport {\n type CommonRerankRequest,\n Reranker,\n type RerankerService,\n type RerankResponse,\n type RerankUsage,\n} from \"@effect-uai/core/Reranker\"\nimport type { JinaRerankerModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * A candidate document. Text models take strings; `jina-reranker-m0` also\n * takes `{ text }` / `{ image }`. One entry is one modality, and image\n * *queries* are local-inference only, so `query` stays a string.\n */\nexport type JinaRerankDocument =\n | string\n | { readonly text: string }\n | { readonly image: ImageSource }\n\n/** Widens {@link CommonRerankRequest} with Jina's multimodal documents. */\nexport type JinaRerankRequest = Omit<CommonRerankRequest, \"model\" | \"documents\"> & {\n readonly model: JinaRerankerModel\n readonly documents: ReadonlyArray<JinaRerankDocument>\n}\n\nexport type JinaRerankerService = {\n readonly rerank: (request: JinaRerankRequest) => Effect.Effect<RerankResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this for multimodal documents; yield the\n * generic `Reranker` tag for provider-portable code. Both are registered by\n * {@link layer}.\n */\nexport class JinaReranker extends Context.Service<JinaReranker, JinaRerankerService>()(\n \"@betalyra/effect-uai/providers/jina/JinaReranker\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\ntype WireDocument = string | { readonly text: string } | { readonly image: string }\n\n// Jina detects URL vs base64 from the value itself, so both go in `image`.\nconst imageSourceToWire: (s: ImageSource) => WireDocument = Match.type<ImageSource>().pipe(\n Match.tag(\"url\", (s): WireDocument => ({ image: s.url })),\n Match.tag(\"base64\", (s): WireDocument => ({ image: s.base64 })),\n Match.tag(\"bytes\", (s): WireDocument => ({ image: Encoding.encodeBase64(s.bytes) })),\n Match.exhaustive,\n)\n\nconst documentToWire: (doc: JinaRerankDocument) => WireDocument =\n Match.type<JinaRerankDocument>().pipe(\n Match.when(Match.string, (s): WireDocument => s),\n Match.when({ text: Match.string }, ({ text }): WireDocument => ({ text })),\n Match.when({ image: Match.any }, ({ image }) => imageSourceToWire(image)),\n Match.exhaustive,\n )\n\ntype WireBody = {\n readonly model: string\n readonly query: string\n readonly documents: ReadonlyArray<WireDocument>\n readonly top_n?: number\n}\n\n// No `return_documents`: the caller holds `documents` and `results[].index`\n// points back into it.\nconst buildBody = (request: JinaRerankRequest): WireBody => ({\n model: request.model,\n query: request.query,\n documents: request.documents.map(documentToWire),\n ...(request.topN !== undefined && { top_n: request.topN }),\n})\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireResult = Schema.Struct({\n index: Schema.Number,\n relevance_score: Schema.Number,\n})\n\nconst WireUsage = Schema.Struct({\n total_tokens: Schema.optional(Schema.Number),\n})\ntype WireUsage = typeof WireUsage.Type\n\nconst WireResponse = Schema.Struct({\n model: Schema.optional(Schema.String),\n usage: Schema.optional(WireUsage),\n results: Schema.Array(WireResult),\n})\ntype WireResponse = typeof WireResponse.Type\n\nconst usageOf = (usage: WireUsage | undefined): RerankUsage =>\n usage?.total_tokens !== undefined ? { totalTokens: usage.total_tokens } : {}\n\n// Sort rather than trust wire order: the score contract promises descending.\nconst toResponse = (wire: WireResponse): RerankResponse => ({\n results: [...wire.results]\n .sort((a, b) => b.relevance_score - a.relevance_score)\n .map((r) => ({ index: r.index, score: r.relevance_score })),\n usage: usageOf(wire.usage),\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 === 413) return new AiError.ContextLengthExceeded({ provider, 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://api.jina.ai/v1\"\n\nconst postRerank = (\n cfg: Config,\n body: WireBody,\n): Effect.Effect<WireResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/rerank`).pipe(\n HttpClientRequest.bearerToken(cfg.apiKey),\n HttpClientRequest.bodyJsonUnsafe(body),\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 rerankImpl =\n (cfg: Config) =>\n (\n request: JinaRerankRequest,\n ): Effect.Effect<RerankResponse, AiError.AiError, HttpClient.HttpClient> =>\n Effect.map(postRerank(cfg, buildBody(request)), toResponse)\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `JinaRerankerService` value. For Layer-based setup, prefer\n * {@link layer}.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<JinaRerankerService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n rerank: (request) =>\n rerankImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer registering both the provider-typed `JinaReranker` tag and the generic\n * `Reranker` tag over one implementation. A `CommonRerankRequest` is\n * structurally a `JinaRerankRequest` with string documents, so the generic\n * registration forwards directly.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<JinaReranker | Reranker, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(JinaReranker, make(cfg))\n const generic = Layer.effect(\n Reranker,\n Effect.map(make(cfg), (s): RerankerService => ({ rerank: (request) => s.rerank(request) })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
- import { JinaEmbeddingModel, JinaEngine } from "./models.mjs";
1
+ import { JinaEmbeddingModel, JinaEngine, JinaRerankerModel } 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 };
4
+ import { t as JinaReranker_d_exports } from "./JinaReranker.mjs";
5
+ export { JinaEmbedding_d_exports as JinaEmbedding, JinaEmbeddingModel, JinaEngine, JinaReader_d_exports as JinaReader, JinaReranker_d_exports as JinaReranker, JinaRerankerModel };
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { t as JinaEmbedding_exports } from "./JinaEmbedding.mjs";
2
2
  import { t as JinaReader_exports } from "./JinaReader.mjs";
3
+ import { t as JinaReranker_exports } from "./JinaReranker.mjs";
3
4
  import "./models.mjs";
4
- export { JinaEmbedding_exports as JinaEmbedding, JinaReader_exports as JinaReader };
5
+ export { JinaEmbedding_exports as JinaEmbedding, JinaReader_exports as JinaReader, JinaReranker_exports as JinaReranker };
package/dist/models.d.mts CHANGED
@@ -19,6 +19,17 @@
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
+ * Known Jina reranker model identifiers.
24
+ *
25
+ * - `jina-reranker-v3.5`: flagship listwise reranker, 131k context.
26
+ * - `jina-reranker-v3`: previous generation, schema-identical.
27
+ * - `jina-reranker-m0`: multimodal, takes `{ text }` / `{ image }`
28
+ * documents (see `JinaRerankRequest`).
29
+ *
30
+ * Reference: https://jina.ai/reranker/
31
+ */
32
+ type JinaRerankerModel = "jina-reranker-v3.5" | "jina-reranker-v3" | "jina-reranker-m0" | (string & {});
22
33
  /**
23
34
  * Reader fetching engine (`X-Engine`). `browser` renders JavaScript,
24
35
  * `curl` is a fast static fetch, `auto` picks per page. Provider-specific,
@@ -26,5 +37,5 @@ type JinaEmbeddingModel = "jina-embeddings-v4" | "jina-embeddings-v5-text-small"
26
37
  */
27
38
  type JinaEngine = "browser" | "curl" | "auto" | (string & {});
28
39
  //#endregion
29
- export { JinaEmbeddingModel, JinaEngine };
40
+ export { JinaEmbeddingModel, JinaEngine, JinaRerankerModel };
30
41
  //# sourceMappingURL=models.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KAmBY;;;;;;KAcA"}
1
+ {"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;KAmBY;;;;;;;;;;;KAmBA;;;;;;KAYA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/jina",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "Jina AI provider for @effect-uai/core (embeddings, rerank, web read).",
5
5
  "keywords": [
6
6
  "ai",
@@ -42,6 +42,10 @@
42
42
  "./JinaReader": {
43
43
  "types": "./dist/JinaReader.d.mts",
44
44
  "import": "./dist/JinaReader.mjs"
45
+ },
46
+ "./JinaReranker": {
47
+ "types": "./dist/JinaReranker.d.mts",
48
+ "import": "./dist/JinaReranker.mjs"
45
49
  }
46
50
  },
47
51
  "publishConfig": {
@@ -50,7 +54,7 @@
50
54
  "devDependencies": {
51
55
  "effect": "4.0.0-rc.111",
52
56
  "typescript": "^7.0.2",
53
- "@effect-uai/core": "0.12.1"
57
+ "@effect-uai/core": "0.14.0"
54
58
  },
55
59
  "peerDependencies": {
56
60
  "@effect-uai/core": ">=0.2.0 <1",
@@ -0,0 +1,203 @@
1
+ import { Context, Effect, Encoding, Layer, Match, type Redacted, Schema } from "effect"
2
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http"
3
+ import * as AiError from "@effect-uai/core/AiError"
4
+ import type { ImageSource } from "@effect-uai/core/Image"
5
+ import {
6
+ type CommonRerankRequest,
7
+ Reranker,
8
+ type RerankerService,
9
+ type RerankResponse,
10
+ type RerankUsage,
11
+ } from "@effect-uai/core/Reranker"
12
+ import type { JinaRerankerModel } from "./models.js"
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Public types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /**
19
+ * A candidate document. Text models take strings; `jina-reranker-m0` also
20
+ * takes `{ text }` / `{ image }`. One entry is one modality, and image
21
+ * *queries* are local-inference only, so `query` stays a string.
22
+ */
23
+ export type JinaRerankDocument =
24
+ | string
25
+ | { readonly text: string }
26
+ | { readonly image: ImageSource }
27
+
28
+ /** Widens {@link CommonRerankRequest} with Jina's multimodal documents. */
29
+ export type JinaRerankRequest = Omit<CommonRerankRequest, "model" | "documents"> & {
30
+ readonly model: JinaRerankerModel
31
+ readonly documents: ReadonlyArray<JinaRerankDocument>
32
+ }
33
+
34
+ export type JinaRerankerService = {
35
+ readonly rerank: (request: JinaRerankRequest) => Effect.Effect<RerankResponse, AiError.AiError>
36
+ }
37
+
38
+ /**
39
+ * Provider-typed service tag. Yield this for multimodal documents; yield the
40
+ * generic `Reranker` tag for provider-portable code. Both are registered by
41
+ * {@link layer}.
42
+ */
43
+ export class JinaReranker extends Context.Service<JinaReranker, JinaRerankerService>()(
44
+ "@betalyra/effect-uai/providers/jina/JinaReranker",
45
+ ) {}
46
+
47
+ export type Config = {
48
+ readonly apiKey: Redacted.Redacted
49
+ readonly baseUrl?: string
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Codec - request body
54
+ // ---------------------------------------------------------------------------
55
+
56
+ type WireDocument = string | { readonly text: string } | { readonly image: string }
57
+
58
+ // Jina detects URL vs base64 from the value itself, so both go in `image`.
59
+ const imageSourceToWire: (s: ImageSource) => WireDocument = Match.type<ImageSource>().pipe(
60
+ Match.tag("url", (s): WireDocument => ({ image: s.url })),
61
+ Match.tag("base64", (s): WireDocument => ({ image: s.base64 })),
62
+ Match.tag("bytes", (s): WireDocument => ({ image: Encoding.encodeBase64(s.bytes) })),
63
+ Match.exhaustive,
64
+ )
65
+
66
+ const documentToWire: (doc: JinaRerankDocument) => WireDocument =
67
+ Match.type<JinaRerankDocument>().pipe(
68
+ Match.when(Match.string, (s): WireDocument => s),
69
+ Match.when({ text: Match.string }, ({ text }): WireDocument => ({ text })),
70
+ Match.when({ image: Match.any }, ({ image }) => imageSourceToWire(image)),
71
+ Match.exhaustive,
72
+ )
73
+
74
+ type WireBody = {
75
+ readonly model: string
76
+ readonly query: string
77
+ readonly documents: ReadonlyArray<WireDocument>
78
+ readonly top_n?: number
79
+ }
80
+
81
+ // No `return_documents`: the caller holds `documents` and `results[].index`
82
+ // points back into it.
83
+ const buildBody = (request: JinaRerankRequest): WireBody => ({
84
+ model: request.model,
85
+ query: request.query,
86
+ documents: request.documents.map(documentToWire),
87
+ ...(request.topN !== undefined && { top_n: request.topN }),
88
+ })
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Codec - response
92
+ // ---------------------------------------------------------------------------
93
+
94
+ const WireResult = Schema.Struct({
95
+ index: Schema.Number,
96
+ relevance_score: Schema.Number,
97
+ })
98
+
99
+ const WireUsage = Schema.Struct({
100
+ total_tokens: Schema.optional(Schema.Number),
101
+ })
102
+ type WireUsage = typeof WireUsage.Type
103
+
104
+ const WireResponse = Schema.Struct({
105
+ model: Schema.optional(Schema.String),
106
+ usage: Schema.optional(WireUsage),
107
+ results: Schema.Array(WireResult),
108
+ })
109
+ type WireResponse = typeof WireResponse.Type
110
+
111
+ const usageOf = (usage: WireUsage | undefined): RerankUsage =>
112
+ usage?.total_tokens !== undefined ? { totalTokens: usage.total_tokens } : {}
113
+
114
+ // Sort rather than trust wire order: the score contract promises descending.
115
+ const toResponse = (wire: WireResponse): RerankResponse => ({
116
+ results: [...wire.results]
117
+ .sort((a, b) => b.relevance_score - a.relevance_score)
118
+ .map((r) => ({ index: r.index, score: r.relevance_score })),
119
+ usage: usageOf(wire.usage),
120
+ })
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // HTTP
124
+ // ---------------------------------------------------------------------------
125
+
126
+ const transportFailure = (cause: unknown): AiError.AiError =>
127
+ new AiError.Unavailable({ provider: "jina", raw: cause })
128
+
129
+ const httpStatusError = (status: number, body: string): AiError.AiError => {
130
+ const provider = "jina"
131
+ const raw = body
132
+ if (status === 429) return new AiError.RateLimited({ provider, raw })
133
+ if (status === 408 || status === 504) return new AiError.Timeout({ provider, raw })
134
+ if (status === 401) return new AiError.AuthFailed({ provider, subtype: "auth", raw })
135
+ if (status === 403) return new AiError.AuthFailed({ provider, subtype: "permission", raw })
136
+ if (status === 402) return new AiError.AuthFailed({ provider, subtype: "billing", raw })
137
+ if (status === 413) return new AiError.ContextLengthExceeded({ provider, raw })
138
+ if (status >= 500) return new AiError.Unavailable({ provider, status, raw })
139
+ return new AiError.InvalidRequest({ provider, raw })
140
+ }
141
+
142
+ const baseUrl = (cfg: Config): string => cfg.baseUrl ?? "https://api.jina.ai/v1"
143
+
144
+ const postRerank = (
145
+ cfg: Config,
146
+ body: WireBody,
147
+ ): Effect.Effect<WireResponse, AiError.AiError, HttpClient.HttpClient> =>
148
+ Effect.gen(function* () {
149
+ const client = yield* HttpClient.HttpClient
150
+ const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/rerank`).pipe(
151
+ HttpClientRequest.bearerToken(cfg.apiKey),
152
+ HttpClientRequest.bodyJsonUnsafe(body),
153
+ )
154
+ const response = yield* client.execute(httpRequest).pipe(Effect.mapError(transportFailure))
155
+ if (response.status >= 400) {
156
+ const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""))
157
+ return yield* httpStatusError(response.status, text)
158
+ }
159
+ const json = yield* response.json.pipe(Effect.mapError(transportFailure))
160
+ return yield* Schema.decodeUnknownEffect(WireResponse)(json).pipe(
161
+ Effect.mapError(transportFailure),
162
+ )
163
+ })
164
+
165
+ const rerankImpl =
166
+ (cfg: Config) =>
167
+ (
168
+ request: JinaRerankRequest,
169
+ ): Effect.Effect<RerankResponse, AiError.AiError, HttpClient.HttpClient> =>
170
+ Effect.map(postRerank(cfg, buildBody(request)), toResponse)
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Constructors
174
+ // ---------------------------------------------------------------------------
175
+
176
+ /**
177
+ * Build a `JinaRerankerService` value. For Layer-based setup, prefer
178
+ * {@link layer}.
179
+ */
180
+ export const make = (
181
+ cfg: Config,
182
+ ): Effect.Effect<JinaRerankerService, never, HttpClient.HttpClient> =>
183
+ Effect.map(HttpClient.HttpClient, (client) => ({
184
+ rerank: (request) =>
185
+ rerankImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),
186
+ }))
187
+
188
+ /**
189
+ * Layer registering both the provider-typed `JinaReranker` tag and the generic
190
+ * `Reranker` tag over one implementation. A `CommonRerankRequest` is
191
+ * structurally a `JinaRerankRequest` with string documents, so the generic
192
+ * registration forwards directly.
193
+ */
194
+ export const layer = (
195
+ cfg: Config,
196
+ ): Layer.Layer<JinaReranker | Reranker, never, HttpClient.HttpClient> => {
197
+ const typed = Layer.effect(JinaReranker, make(cfg))
198
+ const generic = Layer.effect(
199
+ Reranker,
200
+ Effect.map(make(cfg), (s): RerankerService => ({ rerank: (request) => s.rerank(request) })),
201
+ )
202
+ return Layer.merge(typed, generic)
203
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * as JinaEmbedding from "./JinaEmbedding.js"
2
2
  export * as JinaReader from "./JinaReader.js"
3
+ export * as JinaReranker from "./JinaReranker.js"
3
4
  export * from "./models.js"
package/src/models.ts CHANGED
@@ -26,6 +26,23 @@ export type JinaEmbeddingModel =
26
26
  // eslint-disable-next-line @typescript-eslint/ban-types
27
27
  | (string & {})
28
28
 
29
+ /**
30
+ * Known Jina reranker model identifiers.
31
+ *
32
+ * - `jina-reranker-v3.5`: flagship listwise reranker, 131k context.
33
+ * - `jina-reranker-v3`: previous generation, schema-identical.
34
+ * - `jina-reranker-m0`: multimodal, takes `{ text }` / `{ image }`
35
+ * documents (see `JinaRerankRequest`).
36
+ *
37
+ * Reference: https://jina.ai/reranker/
38
+ */
39
+ export type JinaRerankerModel =
40
+ | "jina-reranker-v3.5"
41
+ | "jina-reranker-v3"
42
+ | "jina-reranker-m0"
43
+ // eslint-disable-next-line @typescript-eslint/ban-types
44
+ | (string & {})
45
+
29
46
  /**
30
47
  * Reader fetching engine (`X-Engine`). `browser` renders JavaScript,
31
48
  * `curl` is a fast static fetch, `auto` picks per page. Provider-specific,