@effect-uai/perplexity 0.11.0 → 0.12.1

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.
@@ -4,8 +4,6 @@ import { HttpClient } from "effect/unstable/http";
4
4
  import { DeepResearch, DeepResearchServiceShape } from "@effect-uai/core/DeepResearch";
5
5
  import * as Job from "@effect-uai/core/Job";
6
6
  import { ResearchRequest } from "@effect-uai/core/Research";
7
-
8
- //#region src/PerplexityDeepResearch.d.ts
9
7
  declare namespace PerplexityDeepResearch_d_exports {
10
8
  export { Config, PerplexityDeepResearch, PerplexityDeepResearchService, PerplexityResearchRequest, layer, make };
11
9
  }
@@ -32,7 +30,8 @@ declare const PerplexityDeepResearch_base: Context.ServiceClass<PerplexityDeepRe
32
30
  declare class PerplexityDeepResearch extends PerplexityDeepResearch_base {}
33
31
  type Config = {
34
32
  readonly apiKey: Redacted.Redacted;
35
- readonly baseUrl?: string; /** Poll cadence / overall timeout for the derived poll loops. */
33
+ readonly baseUrl?: string;
34
+ /** Poll cadence / overall timeout for the derived poll loops. */
36
35
  readonly job?: Job.JobConfig;
37
36
  };
38
37
  /** Build a `PerplexityDeepResearchService`. For Layer setup, prefer {@link layer}. */
@@ -1 +1 @@
1
- {"version":3,"file":"PerplexityDeepResearch.d.mts","names":[],"sources":["../src/PerplexityDeepResearch.ts"],"mappings":";;;;;;;;;;;;;;;;;KA2BY,yBAAA,GAA4B,IAAA,CAAK,eAAA;EAAA,SAClC,KAAA,GAAQ,uBAAA;EAAA,SACR,eAAA,GAAkB,yBAAA;AAAA;AAAA,KAGjB,6BAAA,GAAgC,wBAAA,CAAyB,yBAAA;AAAA,cAA0B,2BAAA;;;;;;;;;cAUlF,sBAAA,SAA+B,2BAAA;AAAA,KAKhC,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA,WApBkB;EAAA,SAsBlB,GAAA,GAAM,GAAA,CAAI,SAAA;AAAA;AAnBrB;AAAA,cAqOa,IAAA,GACX,GAAA,EAAK,MAAA,KACJ,MAAA,CAAO,MAAA,CAAO,6BAAA,SAAsC,UAAA,CAAW,UAAA;;;;AAvO6B;;cA+OlF,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,sBAAA,GAAyB,YAAA,SAAqB,UAAA,CAAW,UAAA"}
1
+ {"version":3,"file":"PerplexityDeepResearch.d.mts","names":[],"sources":["../src/PerplexityDeepResearch.ts"],"mappings":";;;;;;;;;;;;;;;KA2BY,4BAA4B,KAAK;WAClC,QAAQ;WACR,kBAAkB;;KAGjB,gCAAgC,yBAAyB;;;;;;;;;;cAUxD,+BAA+B;KAKhC;WACD,QAAQ,SAAS;WACjB;;WAEA,MAAM,IAAI;;;cA4MR,OAAI,KACV,WACJ,OAAO,OAAO,sCAAsC,WAAW;;;;;;cAQrD,QAAK,KACX,WACJ,MAAM,MAAM,yBAAyB,qBAAqB,WAAW"}
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { httpStatusError, transportFailure } from "./http.mjs";
3
3
  import { Context, Effect, Layer, Match, Redacted, Schema } from "effect";
4
4
  import { HttpClient, HttpClientRequest } from "effect/unstable/http";
@@ -82,8 +82,7 @@ const completionToTurn = (wire) => {
82
82
  type: "output_text",
83
83
  text,
84
84
  ...annotations.length > 0 && { annotations }
85
- }],
86
- providerData: wire
85
+ }]
87
86
  }],
88
87
  usage: {
89
88
  ...wire.usage?.prompt_tokens !== void 0 && { input_tokens: wire.usage.prompt_tokens },
@@ -120,7 +119,8 @@ const submitJob = (cfg, request) => Effect.gen(function* () {
120
119
  const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
121
120
  return yield* httpStatusError(response.status, text);
122
121
  }
123
- const wire = yield* decodeJob(yield* response.json.pipe(Effect.mapError(transportFailure))).pipe(Effect.mapError(transportFailure));
122
+ const json = yield* response.json.pipe(Effect.mapError(transportFailure));
123
+ const wire = yield* decodeJob(json).pipe(Effect.mapError(transportFailure));
124
124
  return Job.jobRef("perplexity", wire.id);
125
125
  });
126
126
  const pollJob = (cfg, ref) => Effect.gen(function* () {
@@ -131,7 +131,9 @@ const pollJob = (cfg, ref) => Effect.gen(function* () {
131
131
  const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
132
132
  return yield* httpStatusError(response.status, text);
133
133
  }
134
- return jobStateOf(yield* decodeJob(yield* response.json.pipe(Effect.mapError(transportFailure))).pipe(Effect.mapError(transportFailure)));
134
+ const json = yield* response.json.pipe(Effect.mapError(transportFailure));
135
+ const wire = yield* decodeJob(json).pipe(Effect.mapError(transportFailure));
136
+ return jobStateOf(wire);
135
137
  });
136
138
  const unsupported = (capability) => new AiError.Unsupported({
137
139
  provider: "perplexity",
@@ -1 +1 @@
1
- {"version":3,"file":"PerplexityDeepResearch.mjs","names":[],"sources":["../src/PerplexityDeepResearch.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n DeepResearch,\n type DeepResearchService,\n type DeepResearchServiceShape,\n fromJob,\n type ResearchJobOps,\n} from \"@effect-uai/core/DeepResearch\"\nimport type { Annotation, ContentBlock, HistoryItem, Message } from \"@effect-uai/core/Items\"\nimport * as Job from \"@effect-uai/core/Job\"\nimport type { ResearchJobRef, ResearchRequest, ResearchState } from \"@effect-uai/core/Research\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\nimport type { PerplexityReasoningEffort, PerplexityResearchModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Perplexity-typed research request. Narrows `model` to the sonar research\n * models and adds `reasoningEffort` (a Perplexity knob that also steers how\n * many searches `sonar-deep-research` runs). Provider-portable code uses the\n * generic `DeepResearch` tag instead.\n */\nexport type PerplexityResearchRequest = Omit<ResearchRequest, \"model\"> & {\n readonly model?: PerplexityResearchModel\n readonly reasoningEffort?: PerplexityReasoningEffort\n}\n\nexport type PerplexityDeepResearchService = DeepResearchServiceShape<PerplexityResearchRequest>\n\n/**\n * Provider-typed tag. Yield this for the Perplexity model / `reasoningEffort`\n * knobs; yield the generic `DeepResearch` tag for provider-portable code. The\n * async job is poll-only, so it has no live event stream: `researchStream` /\n * `streamFrom` return the synthesized progress {@link fromJob} builds (a leading\n * search event, then the terminal report). `cancel` fails `Unsupported` (no\n * endpoint). A persisted ref survives a restart (the job is server-backed).\n */\nexport class PerplexityDeepResearch extends Context.Service<\n PerplexityDeepResearch,\n PerplexityDeepResearchService\n>()(\"@betalyra/effect-uai/providers/perplexity/PerplexityDeepResearch\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n /** Poll cadence / overall timeout for the derived poll loops. */\n readonly job?: Job.JobConfig\n}\n\n// ---------------------------------------------------------------------------\n// Wire codec\n// ---------------------------------------------------------------------------\n\nconst WireSearchResult = Schema.Struct({\n title: Schema.String,\n url: Schema.String,\n snippet: Schema.optional(Schema.String),\n})\n\nconst WireChoice = Schema.Struct({\n message: Schema.optional(\n Schema.Struct({ content: Schema.optional(Schema.NullOr(Schema.String)) }),\n ),\n})\n\nconst WireUsage = Schema.Struct({\n prompt_tokens: Schema.optional(Schema.Number),\n completion_tokens: Schema.optional(Schema.Number),\n total_tokens: Schema.optional(Schema.Number),\n})\n\nconst WireCompletion = Schema.Struct({\n choices: Schema.Array(WireChoice),\n search_results: Schema.optional(Schema.Array(WireSearchResult)),\n citations: Schema.optional(Schema.Array(Schema.String)),\n usage: Schema.optional(WireUsage),\n})\ntype WireCompletion = typeof WireCompletion.Type\n\nconst WireJob = Schema.Struct({\n id: Schema.String,\n status: Schema.Literals([\"CREATED\", \"IN_PROGRESS\", \"COMPLETED\", \"FAILED\"]),\n error_message: Schema.optional(Schema.NullOr(Schema.String)),\n response: Schema.optional(Schema.NullOr(WireCompletion)),\n})\ntype WireJob = typeof WireJob.Type\n\nconst decodeJob = Schema.decodeUnknownEffect(WireJob)\n\n// Text of a content block; only the two text-bearing kinds contribute.\nconst blockText = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => b.text,\n output_text: (b) => b.text,\n input_image: () => \"\",\n refusal: () => \"\",\n }),\n)\n\nconst historyToMessages = (\n history: ReadonlyArray<HistoryItem>,\n): ReadonlyArray<{ readonly role: string; readonly content: string }> =>\n history\n .filter((i): i is Message => i.type === \"message\")\n .map((m) => ({ role: m.role, content: m.content.map(blockText).join(\"\") }))\n\n// Perplexity links answers to sources with positional `[n]` markers, so each\n// citation carries a 1-based `marker` (and `cited_text` from the snippet), the\n// provider-agnostic representation of that style. Prefer the richer\n// `search_results`; fall back to the legacy `citations` URL list.\nconst citationsOf = (wire: WireCompletion): ReadonlyArray<Annotation> =>\n wire.search_results !== undefined && wire.search_results.length > 0\n ? wire.search_results.map((r, i) => ({\n type: \"url_citation\" as const,\n url: r.url,\n title: r.title,\n marker: i + 1,\n ...(r.snippet !== undefined && r.snippet !== \"\" && { cited_text: r.snippet }),\n }))\n : (wire.citations ?? []).map((url, i) => ({\n type: \"url_citation\" as const,\n url,\n title: url,\n marker: i + 1,\n }))\n\nconst completionToTurn = (wire: WireCompletion): Turn => {\n const text = wire.choices[0]?.message?.content ?? \"\"\n const annotations = citationsOf(wire)\n return {\n items: [\n {\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"output_text\", text, ...(annotations.length > 0 && { annotations }) }],\n providerData: wire,\n },\n ],\n usage: {\n ...(wire.usage?.prompt_tokens !== undefined && { input_tokens: wire.usage.prompt_tokens }),\n ...(wire.usage?.completion_tokens !== undefined && {\n output_tokens: wire.usage.completion_tokens,\n }),\n ...(wire.usage?.total_tokens !== undefined && { total_tokens: wire.usage.total_tokens }),\n },\n stop_reason: \"stop\",\n }\n}\n\n// Map Perplexity's four job states onto the generic `JobState`, carrying the\n// assembled `Turn` on `Succeeded` and the provider message on `Failed`.\nconst jobStateOf = (wire: WireJob): ResearchState =>\n Match.value(wire.status).pipe(\n Match.when(\"CREATED\", (): ResearchState => ({ _tag: \"Pending\" })),\n Match.when(\"IN_PROGRESS\", (): ResearchState => ({ _tag: \"Running\" })),\n Match.when(\n \"COMPLETED\",\n (): ResearchState =>\n wire.response != null\n ? { _tag: \"Succeeded\", result: completionToTurn(wire.response) }\n : { _tag: \"Failed\", reason: \"job completed with no response body\", raw: wire },\n ),\n Match.when(\n \"FAILED\",\n (): ResearchState => ({\n _tag: \"Failed\",\n ...(wire.error_message != null && { reason: wire.error_message }),\n raw: wire,\n }),\n ),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string => cfg.baseUrl ?? \"https://api.perplexity.ai\"\n\nconst authHeader = (cfg: Config) =>\n HttpClientRequest.setHeader(\"authorization\", `Bearer ${Redacted.value(cfg.apiKey)}`)\n\nconst submitBody = (request: PerplexityResearchRequest): Record<string, unknown> => ({\n request: {\n model: request.model ?? \"sonar-deep-research\",\n messages: historyToMessages(request.history),\n ...(request.reasoningEffort !== undefined && { reasoning_effort: request.reasoningEffort }),\n },\n})\n\nconst submitJob = (\n cfg: Config,\n request: PerplexityResearchRequest,\n): Effect.Effect<ResearchJobRef, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/v1/async/sonar`).pipe(\n authHeader(cfg),\n HttpClientRequest.bodyJsonUnsafe(submitBody(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 const wire = yield* decodeJob(json).pipe(Effect.mapError(transportFailure))\n return Job.jobRef<Turn>(\"perplexity\", wire.id)\n })\n\nconst pollJob = (\n cfg: Config,\n ref: ResearchJobRef,\n): Effect.Effect<ResearchState, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/v1/async/sonar/${ref.id}`).pipe(\n authHeader(cfg),\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 const wire = yield* decodeJob(json).pipe(Effect.mapError(transportFailure))\n return jobStateOf(wire)\n })\n\n// Perplexity exposes no cancel endpoint and no event stream on the async job.\nconst unsupported = (capability: string): AiError.Unsupported =>\n new AiError.Unsupported({\n provider: \"perplexity\",\n capability,\n reason: \"Perplexity's async research job is poll-only and cannot be cancelled.\",\n })\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n// The three job ops. Poll-only, so no `streamFrom`: `fromJob` synthesizes one.\nconst jobOps = (\n cfg: Config,\n client: HttpClient.HttpClient,\n): ResearchJobOps<PerplexityResearchRequest> => {\n const withClient = <A>(e: Effect.Effect<A, AiError.AiError, HttpClient.HttpClient>) =>\n Effect.provideService(e, HttpClient.HttpClient, client)\n return {\n submit: (request) => withClient(submitJob(cfg, request)),\n poll: (ref) => withClient(pollJob(cfg, ref)),\n cancel: () => Effect.fail(unsupported(\"cancel\")),\n }\n}\n\n/** Build a `PerplexityDeepResearchService`. For Layer setup, prefer {@link layer}. */\nexport const make = (\n cfg: Config,\n): Effect.Effect<PerplexityDeepResearchService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => fromJob(jobOps(cfg, client), cfg.job))\n\n/**\n * Layer registering the provider-typed `PerplexityDeepResearch` tag and the\n * generic `DeepResearch` tag. No capability markers: the whole surface (poll,\n * collect, synthesized stream) is universal.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<PerplexityDeepResearch | DeepResearch, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(PerplexityDeepResearch, make(cfg))\n const generic = Layer.effect(\n DeepResearch,\n Effect.map(HttpClient.HttpClient, (client): DeepResearchService => {\n const ops = jobOps(cfg, client)\n return fromJob<ResearchRequest>(\n {\n submit: (request) => ops.submit(request as PerplexityResearchRequest),\n poll: ops.poll,\n cancel: ops.cancel,\n },\n cfg.job,\n )\n }),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PerplexityDeepResearch.mjs","names":[],"sources":["../src/PerplexityDeepResearch.ts"],"sourcesContent":["import { Context, Effect, Layer, Match, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n DeepResearch,\n type DeepResearchService,\n type DeepResearchServiceShape,\n fromJob,\n type ResearchJobOps,\n} from \"@effect-uai/core/DeepResearch\"\nimport type { Annotation, ContentBlock, HistoryItem, Message } from \"@effect-uai/core/Items\"\nimport * as Job from \"@effect-uai/core/Job\"\nimport type { ResearchJobRef, ResearchRequest, ResearchState } from \"@effect-uai/core/Research\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\nimport type { PerplexityReasoningEffort, PerplexityResearchModel } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Perplexity-typed research request. Narrows `model` to the sonar research\n * models and adds `reasoningEffort` (a Perplexity knob that also steers how\n * many searches `sonar-deep-research` runs). Provider-portable code uses the\n * generic `DeepResearch` tag instead.\n */\nexport type PerplexityResearchRequest = Omit<ResearchRequest, \"model\"> & {\n readonly model?: PerplexityResearchModel\n readonly reasoningEffort?: PerplexityReasoningEffort\n}\n\nexport type PerplexityDeepResearchService = DeepResearchServiceShape<PerplexityResearchRequest>\n\n/**\n * Provider-typed tag. Yield this for the Perplexity model / `reasoningEffort`\n * knobs; yield the generic `DeepResearch` tag for provider-portable code. The\n * async job is poll-only, so it has no live event stream: `researchStream` /\n * `streamFrom` return the synthesized progress {@link fromJob} builds (a leading\n * search event, then the terminal report). `cancel` fails `Unsupported` (no\n * endpoint). A persisted ref survives a restart (the job is server-backed).\n */\nexport class PerplexityDeepResearch extends Context.Service<\n PerplexityDeepResearch,\n PerplexityDeepResearchService\n>()(\"@betalyra/effect-uai/providers/perplexity/PerplexityDeepResearch\") {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n /** Poll cadence / overall timeout for the derived poll loops. */\n readonly job?: Job.JobConfig\n}\n\n// ---------------------------------------------------------------------------\n// Wire codec\n// ---------------------------------------------------------------------------\n\nconst WireSearchResult = Schema.Struct({\n title: Schema.String,\n url: Schema.String,\n snippet: Schema.optional(Schema.String),\n})\n\nconst WireChoice = Schema.Struct({\n message: Schema.optional(\n Schema.Struct({ content: Schema.optional(Schema.NullOr(Schema.String)) }),\n ),\n})\n\nconst WireUsage = Schema.Struct({\n prompt_tokens: Schema.optional(Schema.Number),\n completion_tokens: Schema.optional(Schema.Number),\n total_tokens: Schema.optional(Schema.Number),\n})\n\nconst WireCompletion = Schema.Struct({\n choices: Schema.Array(WireChoice),\n search_results: Schema.optional(Schema.Array(WireSearchResult)),\n citations: Schema.optional(Schema.Array(Schema.String)),\n usage: Schema.optional(WireUsage),\n})\ntype WireCompletion = typeof WireCompletion.Type\n\nconst WireJob = Schema.Struct({\n id: Schema.String,\n status: Schema.Literals([\"CREATED\", \"IN_PROGRESS\", \"COMPLETED\", \"FAILED\"]),\n error_message: Schema.optional(Schema.NullOr(Schema.String)),\n response: Schema.optional(Schema.NullOr(WireCompletion)),\n})\ntype WireJob = typeof WireJob.Type\n\nconst decodeJob = Schema.decodeUnknownEffect(WireJob)\n\n// Text of a content block; only the two text-bearing kinds contribute.\nconst blockText = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => b.text,\n output_text: (b) => b.text,\n input_image: () => \"\",\n refusal: () => \"\",\n }),\n)\n\nconst historyToMessages = (\n history: ReadonlyArray<HistoryItem>,\n): ReadonlyArray<{ readonly role: string; readonly content: string }> =>\n history\n .filter((i): i is Message => i.type === \"message\")\n .map((m) => ({ role: m.role, content: m.content.map(blockText).join(\"\") }))\n\n// Perplexity links answers to sources with positional `[n]` markers, so each\n// citation carries a 1-based `marker` (and `cited_text` from the snippet), the\n// provider-agnostic representation of that style. Prefer the richer\n// `search_results`; fall back to the legacy `citations` URL list.\nconst citationsOf = (wire: WireCompletion): ReadonlyArray<Annotation> =>\n wire.search_results !== undefined && wire.search_results.length > 0\n ? wire.search_results.map((r, i) => ({\n type: \"url_citation\" as const,\n url: r.url,\n title: r.title,\n marker: i + 1,\n ...(r.snippet !== undefined && r.snippet !== \"\" && { cited_text: r.snippet }),\n }))\n : (wire.citations ?? []).map((url, i) => ({\n type: \"url_citation\" as const,\n url,\n title: url,\n marker: i + 1,\n }))\n\nconst completionToTurn = (wire: WireCompletion): Turn => {\n const text = wire.choices[0]?.message?.content ?? \"\"\n const annotations = citationsOf(wire)\n return {\n items: [\n {\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"output_text\", text, ...(annotations.length > 0 && { annotations }) }],\n },\n ],\n usage: {\n ...(wire.usage?.prompt_tokens !== undefined && { input_tokens: wire.usage.prompt_tokens }),\n ...(wire.usage?.completion_tokens !== undefined && {\n output_tokens: wire.usage.completion_tokens,\n }),\n ...(wire.usage?.total_tokens !== undefined && { total_tokens: wire.usage.total_tokens }),\n },\n stop_reason: \"stop\",\n }\n}\n\n// Map Perplexity's four job states onto the generic `JobState`, carrying the\n// assembled `Turn` on `Succeeded` and the provider message on `Failed`.\nconst jobStateOf = (wire: WireJob): ResearchState =>\n Match.value(wire.status).pipe(\n Match.when(\"CREATED\", (): ResearchState => ({ _tag: \"Pending\" })),\n Match.when(\"IN_PROGRESS\", (): ResearchState => ({ _tag: \"Running\" })),\n Match.when(\"COMPLETED\", (): ResearchState =>\n wire.response != null\n ? { _tag: \"Succeeded\", result: completionToTurn(wire.response) }\n : { _tag: \"Failed\", reason: \"job completed with no response body\", raw: wire },\n ),\n Match.when(\"FAILED\", (): ResearchState => ({\n _tag: \"Failed\",\n ...(wire.error_message != null && { reason: wire.error_message }),\n raw: wire,\n })),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst baseUrl = (cfg: Config): string => cfg.baseUrl ?? \"https://api.perplexity.ai\"\n\nconst authHeader = (cfg: Config) =>\n HttpClientRequest.setHeader(\"authorization\", `Bearer ${Redacted.value(cfg.apiKey)}`)\n\nconst submitBody = (request: PerplexityResearchRequest): Record<string, unknown> => ({\n request: {\n model: request.model ?? \"sonar-deep-research\",\n messages: historyToMessages(request.history),\n ...(request.reasoningEffort !== undefined && { reasoning_effort: request.reasoningEffort }),\n },\n})\n\nconst submitJob = (\n cfg: Config,\n request: PerplexityResearchRequest,\n): Effect.Effect<ResearchJobRef, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.post(`${baseUrl(cfg)}/v1/async/sonar`).pipe(\n authHeader(cfg),\n HttpClientRequest.bodyJsonUnsafe(submitBody(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 const wire = yield* decodeJob(json).pipe(Effect.mapError(transportFailure))\n return Job.jobRef<Turn>(\"perplexity\", wire.id)\n })\n\nconst pollJob = (\n cfg: Config,\n ref: ResearchJobRef,\n): Effect.Effect<ResearchState, AiError.AiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const httpRequest = HttpClientRequest.get(`${baseUrl(cfg)}/v1/async/sonar/${ref.id}`).pipe(\n authHeader(cfg),\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 const wire = yield* decodeJob(json).pipe(Effect.mapError(transportFailure))\n return jobStateOf(wire)\n })\n\n// Perplexity exposes no cancel endpoint and no event stream on the async job.\nconst unsupported = (capability: string): AiError.Unsupported =>\n new AiError.Unsupported({\n provider: \"perplexity\",\n capability,\n reason: \"Perplexity's async research job is poll-only and cannot be cancelled.\",\n })\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n// The three job ops. Poll-only, so no `streamFrom`: `fromJob` synthesizes one.\nconst jobOps = (\n cfg: Config,\n client: HttpClient.HttpClient,\n): ResearchJobOps<PerplexityResearchRequest> => {\n const withClient = <A>(e: Effect.Effect<A, AiError.AiError, HttpClient.HttpClient>) =>\n Effect.provideService(e, HttpClient.HttpClient, client)\n return {\n submit: (request) => withClient(submitJob(cfg, request)),\n poll: (ref) => withClient(pollJob(cfg, ref)),\n cancel: () => Effect.fail(unsupported(\"cancel\")),\n }\n}\n\n/** Build a `PerplexityDeepResearchService`. For Layer setup, prefer {@link layer}. */\nexport const make = (\n cfg: Config,\n): Effect.Effect<PerplexityDeepResearchService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => fromJob(jobOps(cfg, client), cfg.job))\n\n/**\n * Layer registering the provider-typed `PerplexityDeepResearch` tag and the\n * generic `DeepResearch` tag. No capability markers: the whole surface (poll,\n * collect, synthesized stream) is universal.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<PerplexityDeepResearch | DeepResearch, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(PerplexityDeepResearch, make(cfg))\n const generic = Layer.effect(\n DeepResearch,\n Effect.map(HttpClient.HttpClient, (client): DeepResearchService => {\n const ops = jobOps(cfg, client)\n return fromJob<ResearchRequest>(\n {\n submit: (request) => ops.submit(request as PerplexityResearchRequest),\n poll: ops.poll,\n cancel: ops.cancel,\n },\n cfg.job,\n )\n }),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
@@ -3,8 +3,6 @@ import { Context, Effect, Layer, Redacted } from "effect";
3
3
  import { HttpClient } from "effect/unstable/http";
4
4
  import * as AiError from "@effect-uai/core/AiError";
5
5
  import { CommonSearchRequest, SearchResponse, WebSearch } from "@effect-uai/core/WebSearch";
6
-
7
- //#region src/PerplexitySearch.d.ts
8
6
  declare namespace PerplexitySearch_d_exports {
9
7
  export { Config, PerplexitySearch, PerplexitySearchRequest, PerplexitySearchService, layer, make };
10
8
  }
@@ -15,7 +13,13 @@ declare namespace PerplexitySearch_d_exports {
15
13
  * web directly.
16
14
  */
17
15
  type PerplexitySearchRequest = CommonSearchRequest & {
18
- /** Depth / cost trade-off - see {@link PerplexitySearchContextSize}. */readonly searchContextSize?: PerplexitySearchContextSize; /** Cap on tokens extracted per result page (1..1_000_000). */
16
+ /**
17
+ * Depth / cost trade-off for per-page extraction - see
18
+ * {@link PerplexitySearchContextSize}. Mutually exclusive with
19
+ * `maxTokensPerPage`; when both are set the token cap wins (see the codec).
20
+ */
21
+ readonly searchContextSize?: PerplexitySearchContextSize;
22
+ /** Cap on tokens extracted per result page (1..1_000_000). */
19
23
  readonly maxTokensPerPage?: number;
20
24
  };
21
25
  type PerplexitySearchService = {
@@ -1 +1 @@
1
- {"version":3,"file":"PerplexitySearch.d.mts","names":[],"sources":["../src/PerplexitySearch.ts"],"mappings":";;;;;;;;;;;;;;;;KAsBY,uBAAA,GAA0B,mBAAA;mFAE3B,iBAAA,GAAoB,2BAAA;WAEpB,gBAAA;AAAA;AAAA,KAGC,uBAAA;EAAA,SACD,MAAA,GACP,OAAA,EAAS,uBAAA,KACN,MAAA,CAAO,MAAA,CAAO,cAAA,EAAgB,OAAA,CAAQ,OAAA;AAAA;AAAA,cAC5C,qBAAA;;;;;;cAOY,gBAAA,SAAyB,qBAAA;AAAA,KAI1B,MAAA;EAAA,SACD,MAAA,EAAQ,QAAA,CAAS,QAAA;EAAA,SACjB,OAAA;AAAA;;;;;cAkKE,IAAA,GACX,GAAA,EAAK,MAAA,KACJ,MAAA,CAAO,MAAA,CAAO,uBAAA,SAAgC,UAAA,CAAW,UAAA;;;;;;;cAY/C,KAAA,GACX,GAAA,EAAK,MAAA,KACJ,KAAA,CAAM,KAAA,CAAM,gBAAA,GAAmB,SAAA,SAAkB,UAAA,CAAW,UAAA"}
1
+ {"version":3,"file":"PerplexitySearch.d.mts","names":[],"sources":["../src/PerplexitySearch.ts"],"mappings":";;;;;;;;;;;;;;KAsBY,0BAA0B;;;;;;WAM3B,oBAAoB;;WAEpB;;KAGC;WACD,SACP,SAAS,4BACN,OAAO,OAAO,gBAAgB,QAAQ;;;;;;;;cAQhC,yBAAyB;KAI1B;WACD,QAAQ,SAAS;WACjB;;;;;;cAoKE,OAAI,KACV,WACJ,OAAO,OAAO,gCAAgC,WAAW;;;;;;;cAY/C,QAAK,KACX,WACJ,MAAM,MAAM,mBAAmB,kBAAkB,WAAW"}
@@ -1,4 +1,4 @@
1
- import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import { Context, DateTime, Effect, Layer, Option, Schema } from "effect";
3
3
  import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
4
  import * as AiError from "@effect-uai/core/AiError";
@@ -40,8 +40,7 @@ const buildBody = (request) => {
40
40
  ...request.recency !== void 0 && { search_recency_filter: request.recency },
41
41
  ...request.startDate !== void 0 && { search_after_date_filter: formatMmDdYyyy(request.startDate) },
42
42
  ...request.endDate !== void 0 && { search_before_date_filter: formatMmDdYyyy(request.endDate) },
43
- ...request.searchContextSize !== void 0 && { search_context_size: request.searchContextSize },
44
- ...request.maxTokensPerPage !== void 0 && { max_tokens_per_page: request.maxTokensPerPage }
43
+ ...request.maxTokensPerPage !== void 0 ? { max_tokens_per_page: request.maxTokensPerPage } : request.searchContextSize !== void 0 && { search_context_size: request.searchContextSize }
45
44
  };
46
45
  };
47
46
  const WireResult = Schema.Struct({
@@ -1 +1 @@
1
- {"version":3,"file":"PerplexitySearch.mjs","names":[],"sources":["../src/PerplexitySearch.ts"],"sourcesContent":["import { Context, DateTime, Effect, Layer, Option, type Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonSearchRequest,\n type SearchResponse,\n type SearchResult,\n WebSearch,\n type WebSearchService,\n} from \"@effect-uai/core/WebSearch\"\nimport type { PerplexitySearchContextSize } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Perplexity-typed search request. Widens {@link CommonSearchRequest} with\n * the provider's own knobs; the common fields map onto Perplexity's wire\n * filters (see the codec below). There is no `model` - `/search` ranks the\n * web directly.\n */\nexport type PerplexitySearchRequest = CommonSearchRequest & {\n /** Depth / cost trade-off - see {@link PerplexitySearchContextSize}. */\n readonly searchContextSize?: PerplexitySearchContextSize\n /** Cap on tokens extracted per result page (1..1_000_000). */\n readonly maxTokensPerPage?: number\n}\n\nexport type PerplexitySearchService = {\n readonly search: (\n request: PerplexitySearchRequest,\n ) => Effect.Effect<SearchResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this for Perplexity-typed search\n * (`searchContextSize`, `maxTokensPerPage`); yield the generic `WebSearch`\n * tag for provider-portable code. Both are registered by {@link layer}.\n */\nexport class PerplexitySearch extends Context.Service<PerplexitySearch, PerplexitySearchService>()(\n \"@betalyra/effect-uai/providers/perplexity/PerplexitySearch\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\ntype WireBody = {\n readonly query: string\n readonly max_results?: number\n readonly country?: string\n readonly search_language_filter?: ReadonlyArray<string>\n readonly search_domain_filter?: ReadonlyArray<string>\n readonly search_recency_filter?: string\n readonly search_after_date_filter?: string\n readonly search_before_date_filter?: string\n readonly search_context_size?: string\n readonly max_tokens_per_page?: number\n}\n\n/** Perplexity wants dates as `MM/DD/YYYY`, in UTC. */\nconst formatMmDdYyyy = (dt: DateTime.DateTime): string => {\n const d = DateTime.toDateUtc(dt)\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\")\n const dd = String(d.getUTCDate()).padStart(2, \"0\")\n return `${mm}/${dd}/${d.getUTCFullYear()}`\n}\n\n/**\n * Perplexity has no separate include / exclude fields - it takes one\n * `search_domain_filter` array where an exclusion is a `-` prefix. Lossless\n * shape transform, so done silently (bucket 3).\n */\nconst domainFilter = (\n include: ReadonlyArray<string> | undefined,\n exclude: ReadonlyArray<string> | undefined,\n): ReadonlyArray<string> | undefined => {\n const all = [...(include ?? []), ...(exclude ?? []).map((d) => `-${d}`)]\n return all.length > 0 ? all : undefined\n}\n\nconst buildBody = (request: PerplexitySearchRequest): WireBody => {\n const domains = domainFilter(request.includeDomains, request.excludeDomains)\n return {\n query: request.query,\n ...(request.maxResults !== undefined && { max_results: request.maxResults }),\n ...(request.country !== undefined && { country: request.country }),\n ...(request.language !== undefined && { search_language_filter: [request.language] }),\n ...(domains !== undefined && { search_domain_filter: domains }),\n ...(request.recency !== undefined && { search_recency_filter: request.recency }),\n ...(request.startDate !== undefined && {\n search_after_date_filter: formatMmDdYyyy(request.startDate),\n }),\n ...(request.endDate !== undefined && {\n search_before_date_filter: formatMmDdYyyy(request.endDate),\n }),\n ...(request.searchContextSize !== undefined && {\n search_context_size: request.searchContextSize,\n }),\n ...(request.maxTokensPerPage !== undefined && {\n max_tokens_per_page: request.maxTokensPerPage,\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireResult = Schema.Struct({\n title: Schema.String,\n url: Schema.String,\n snippet: Schema.optional(Schema.String),\n date: Schema.optional(Schema.NullOr(Schema.String)),\n last_updated: Schema.optional(Schema.NullOr(Schema.String)),\n})\ntype WireResult = typeof WireResult.Type\n\nconst WireResponse = Schema.Struct({\n results: Schema.Array(WireResult),\n id: Schema.optional(Schema.String),\n})\ntype WireResponse = typeof WireResponse.Type\n\n/** Lenient parse of Perplexity's date strings; unparseable -> omitted. */\nconst parseDate = (s: string | null | undefined): DateTime.DateTime | undefined =>\n s == null ? undefined : Option.getOrUndefined(DateTime.make(s))\n\nconst toResult = (r: WireResult): SearchResult => {\n const publishedDate = parseDate(r.date)\n return {\n url: r.url,\n title: r.title,\n // Perplexity returns no relevance score, so `score` stays undefined.\n ...(r.snippet !== undefined && { snippet: r.snippet }),\n ...(publishedDate !== undefined && { publishedDate }),\n raw: r,\n }\n}\n\nconst toResponse = (wire: WireResponse): SearchResponse => ({\n results: wire.results.map(toResult),\n raw: wire,\n})\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"perplexity\", raw: cause })\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"perplexity\"\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://api.perplexity.ai\"\n\nconst postSearch = (\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)}/search`).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 searchImpl =\n (cfg: Config) =>\n (\n request: PerplexitySearchRequest,\n ): Effect.Effect<SearchResponse, AiError.AiError, HttpClient.HttpClient> =>\n postSearch(cfg, buildBody(request)).pipe(Effect.map(toResponse))\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `PerplexitySearchService` value. For Layer-based setup, prefer\n * {@link layer}.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<PerplexitySearchService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n search: (request) =>\n searchImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer registering both the provider-typed `PerplexitySearch` tag and the\n * generic `WebSearch` tag over one implementation. A `CommonSearchRequest`\n * is structurally a `PerplexitySearchRequest` with the provider extras\n * unset, so the generic registration forwards directly.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<PerplexitySearch | WebSearch, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(PerplexitySearch, make(cfg))\n const generic = Layer.effect(\n WebSearch,\n Effect.map(make(cfg), (s): WebSearchService => ({ search: (request) => s.search(request) })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PerplexitySearch.mjs","names":[],"sources":["../src/PerplexitySearch.ts"],"sourcesContent":["import { Context, DateTime, Effect, Layer, Option, type Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport {\n type CommonSearchRequest,\n type SearchResponse,\n type SearchResult,\n WebSearch,\n type WebSearchService,\n} from \"@effect-uai/core/WebSearch\"\nimport type { PerplexitySearchContextSize } from \"./models.js\"\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Perplexity-typed search request. Widens {@link CommonSearchRequest} with\n * the provider's own knobs; the common fields map onto Perplexity's wire\n * filters (see the codec below). There is no `model` - `/search` ranks the\n * web directly.\n */\nexport type PerplexitySearchRequest = CommonSearchRequest & {\n /**\n * Depth / cost trade-off for per-page extraction - see\n * {@link PerplexitySearchContextSize}. Mutually exclusive with\n * `maxTokensPerPage`; when both are set the token cap wins (see the codec).\n */\n readonly searchContextSize?: PerplexitySearchContextSize\n /** Cap on tokens extracted per result page (1..1_000_000). */\n readonly maxTokensPerPage?: number\n}\n\nexport type PerplexitySearchService = {\n readonly search: (\n request: PerplexitySearchRequest,\n ) => Effect.Effect<SearchResponse, AiError.AiError>\n}\n\n/**\n * Provider-typed service tag. Yield this for Perplexity-typed search\n * (`searchContextSize`, `maxTokensPerPage`); yield the generic `WebSearch`\n * tag for provider-portable code. Both are registered by {@link layer}.\n */\nexport class PerplexitySearch extends Context.Service<PerplexitySearch, PerplexitySearchService>()(\n \"@betalyra/effect-uai/providers/perplexity/PerplexitySearch\",\n) {}\n\nexport type Config = {\n readonly apiKey: Redacted.Redacted\n readonly baseUrl?: string\n}\n\n// ---------------------------------------------------------------------------\n// Codec - request body\n// ---------------------------------------------------------------------------\n\ntype WireBody = {\n readonly query: string\n readonly max_results?: number\n readonly country?: string\n readonly search_language_filter?: ReadonlyArray<string>\n readonly search_domain_filter?: ReadonlyArray<string>\n readonly search_recency_filter?: string\n readonly search_after_date_filter?: string\n readonly search_before_date_filter?: string\n readonly search_context_size?: string\n readonly max_tokens_per_page?: number\n}\n\n/** Perplexity wants dates as `MM/DD/YYYY`, in UTC. */\nconst formatMmDdYyyy = (dt: DateTime.DateTime): string => {\n const d = DateTime.toDateUtc(dt)\n const mm = String(d.getUTCMonth() + 1).padStart(2, \"0\")\n const dd = String(d.getUTCDate()).padStart(2, \"0\")\n return `${mm}/${dd}/${d.getUTCFullYear()}`\n}\n\n/**\n * Perplexity has no separate include / exclude fields - it takes one\n * `search_domain_filter` array where an exclusion is a `-` prefix. Lossless\n * shape transform, so done silently (bucket 3).\n */\nconst domainFilter = (\n include: ReadonlyArray<string> | undefined,\n exclude: ReadonlyArray<string> | undefined,\n): ReadonlyArray<string> | undefined => {\n const all = [...(include ?? []), ...(exclude ?? []).map((d) => `-${d}`)]\n return all.length > 0 ? all : undefined\n}\n\nconst buildBody = (request: PerplexitySearchRequest): WireBody => {\n const domains = domainFilter(request.includeDomains, request.excludeDomains)\n return {\n query: request.query,\n ...(request.maxResults !== undefined && { max_results: request.maxResults }),\n ...(request.country !== undefined && { country: request.country }),\n ...(request.language !== undefined && { search_language_filter: [request.language] }),\n ...(domains !== undefined && { search_domain_filter: domains }),\n ...(request.recency !== undefined && { search_recency_filter: request.recency }),\n ...(request.startDate !== undefined && {\n search_after_date_filter: formatMmDdYyyy(request.startDate),\n }),\n ...(request.endDate !== undefined && {\n search_before_date_filter: formatMmDdYyyy(request.endDate),\n }),\n // `/search` rejects `search_context_size` alongside a token cap (both govern\n // per-page extraction) with a 500, so the two are mutually exclusive. The\n // token cap is the finer control, so it wins when both are set.\n ...(request.maxTokensPerPage !== undefined\n ? { max_tokens_per_page: request.maxTokensPerPage }\n : request.searchContextSize !== undefined && {\n search_context_size: request.searchContextSize,\n }),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Codec - response\n// ---------------------------------------------------------------------------\n\nconst WireResult = Schema.Struct({\n title: Schema.String,\n url: Schema.String,\n snippet: Schema.optional(Schema.String),\n date: Schema.optional(Schema.NullOr(Schema.String)),\n last_updated: Schema.optional(Schema.NullOr(Schema.String)),\n})\ntype WireResult = typeof WireResult.Type\n\nconst WireResponse = Schema.Struct({\n results: Schema.Array(WireResult),\n id: Schema.optional(Schema.String),\n})\ntype WireResponse = typeof WireResponse.Type\n\n/** Lenient parse of Perplexity's date strings; unparseable -> omitted. */\nconst parseDate = (s: string | null | undefined): DateTime.DateTime | undefined =>\n s == null ? undefined : Option.getOrUndefined(DateTime.make(s))\n\nconst toResult = (r: WireResult): SearchResult => {\n const publishedDate = parseDate(r.date)\n return {\n url: r.url,\n title: r.title,\n // Perplexity returns no relevance score, so `score` stays undefined.\n ...(r.snippet !== undefined && { snippet: r.snippet }),\n ...(publishedDate !== undefined && { publishedDate }),\n raw: r,\n }\n}\n\nconst toResponse = (wire: WireResponse): SearchResponse => ({\n results: wire.results.map(toResult),\n raw: wire,\n})\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nconst transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"perplexity\", raw: cause })\n\nconst httpStatusError = (status: number, body: string): AiError.AiError => {\n const provider = \"perplexity\"\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://api.perplexity.ai\"\n\nconst postSearch = (\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)}/search`).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 searchImpl =\n (cfg: Config) =>\n (\n request: PerplexitySearchRequest,\n ): Effect.Effect<SearchResponse, AiError.AiError, HttpClient.HttpClient> =>\n postSearch(cfg, buildBody(request)).pipe(Effect.map(toResponse))\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `PerplexitySearchService` value. For Layer-based setup, prefer\n * {@link layer}.\n */\nexport const make = (\n cfg: Config,\n): Effect.Effect<PerplexitySearchService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => ({\n search: (request) =>\n searchImpl(cfg)(request).pipe(Effect.provideService(HttpClient.HttpClient, client)),\n }))\n\n/**\n * Layer registering both the provider-typed `PerplexitySearch` tag and the\n * generic `WebSearch` tag over one implementation. A `CommonSearchRequest`\n * is structurally a `PerplexitySearchRequest` with the provider extras\n * unset, so the generic registration forwards directly.\n */\nexport const layer = (\n cfg: Config,\n): Layer.Layer<PerplexitySearch | WebSearch, never, HttpClient.HttpClient> => {\n const typed = Layer.effect(PerplexitySearch, make(cfg))\n const generic = Layer.effect(\n WebSearch,\n Effect.map(make(cfg), (s): WebSearchService => ({ search: (request) => s.search(request) })),\n )\n return Layer.merge(typed, generic)\n}\n"],"mappings":"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"}
package/dist/http.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as AiError from "@effect-uai/core/AiError";
2
-
3
2
  //#region src/http.d.ts
4
3
  /** Transport-level failure (connection dropped, DNS, etc.) before a response. */
5
4
  declare const transportFailure: (cause: unknown) => AiError.AiError;
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.mts","names":[],"sources":["../src/http.ts"],"mappings":";;;;cAKa,gBAAA,GAAoB,KAAA,cAAiB,OAAA,CAAQ,OAAA;AAA1D;AAAA,cAIa,eAAA,GAAmB,MAAA,UAAgB,IAAA,aAAe,OAAA,CAAQ,OAAA"}
1
+ {"version":3,"file":"http.d.mts","names":[],"sources":["../src/http.ts"],"mappings":";;;cAKa,mBAAgB,mBAAqB,QAAQ;;cAI7C,kBAAe,gBAAkB,iBAAiB,QAAQ"}
package/dist/http.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"http.mjs","names":[],"sources":["../src/http.ts"],"sourcesContent":["import * as AiError from \"@effect-uai/core/AiError\"\n\nconst provider = \"perplexity\"\n\n/** Transport-level failure (connection dropped, DNS, etc.) before a response. */\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider, raw: cause })\n\n/** Map a >=400 HTTP status to the nearest `AiError`. */\nexport const httpStatusError = (status: number, body: string): AiError.AiError => {\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"],"mappings":";;AAEA,MAAM,WAAW;;AAGjB,MAAa,oBAAoB,UAC/B,IAAI,QAAQ,YAAY;CAAE;CAAU,KAAK;CAAO,CAAC;;AAGnD,MAAa,mBAAmB,QAAgB,SAAkC;CAChF,MAAM,MAAM;AACZ,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,YAAY;EAAE;EAAU;EAAK,CAAC;AACrE,KAAI,WAAW,OAAO,WAAW,IAAK,QAAO,IAAI,QAAQ,QAAQ;EAAE;EAAU;EAAK,CAAC;AACnF,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAQ;EAAK,CAAC;AACrF,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAc;EAAK,CAAC;AAC3F,KAAI,WAAW,IAAK,QAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAW;EAAK,CAAC;AACxF,KAAI,UAAU,IAAK,QAAO,IAAI,QAAQ,YAAY;EAAE;EAAU;EAAQ;EAAK,CAAC;AAC5E,QAAO,IAAI,QAAQ,eAAe;EAAE;EAAU;EAAK,CAAC"}
1
+ {"version":3,"file":"http.mjs","names":[],"sources":["../src/http.ts"],"sourcesContent":["import * as AiError from \"@effect-uai/core/AiError\"\n\nconst provider = \"perplexity\"\n\n/** Transport-level failure (connection dropped, DNS, etc.) before a response. */\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider, raw: cause })\n\n/** Map a >=400 HTTP status to the nearest `AiError`. */\nexport const httpStatusError = (status: number, body: string): AiError.AiError => {\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"],"mappings":";;AAEA,MAAM,WAAW;;AAGjB,MAAa,oBAAoB,UAC/B,IAAI,QAAQ,YAAY;CAAE;CAAU,KAAK;AAAM,CAAC;;AAGlD,MAAa,mBAAmB,QAAgB,SAAkC;CAChF,MAAM,MAAM;CACZ,IAAI,WAAW,KAAK,OAAO,IAAI,QAAQ,YAAY;EAAE;EAAU;CAAI,CAAC;CACpE,IAAI,WAAW,OAAO,WAAW,KAAK,OAAO,IAAI,QAAQ,QAAQ;EAAE;EAAU;CAAI,CAAC;CAClF,IAAI,WAAW,KAAK,OAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAQ;CAAI,CAAC;CACpF,IAAI,WAAW,KAAK,OAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAc;CAAI,CAAC;CAC1F,IAAI,WAAW,KAAK,OAAO,IAAI,QAAQ,WAAW;EAAE;EAAU,SAAS;EAAW;CAAI,CAAC;CACvF,IAAI,UAAU,KAAK,OAAO,IAAI,QAAQ,YAAY;EAAE;EAAU;EAAQ;CAAI,CAAC;CAC3E,OAAO,IAAI,QAAQ,eAAe;EAAE;EAAU;CAAI,CAAC;AACrD"}
package/dist/models.d.mts CHANGED
@@ -1,15 +1,16 @@
1
1
  //#region src/models.d.ts
2
2
  /**
3
- * Perplexity's Search API takes no model identifier - unlike chat
4
- * completions, `/search` ranks the web directly. The one mode knob is the
5
- * search-context size, which trades latency / cost for depth.
3
+ * Depth / cost knob for Perplexity's Search API (`/search`), controlling how
4
+ * much content is extracted per result page. It trades latency / cost for
5
+ * depth:
6
6
  *
7
- * - `low` — fastest, fewest pages fetched.
8
- * - `medium` — balanced.
9
- * - `high` — most thorough (Perplexity's default).
7
+ * - `low`: short passages most relevant to the query.
8
+ * - `medium`: a balanced amount of content per document.
9
+ * - `high`: detailed content (Perplexity's default).
10
10
  *
11
- * It is a provider-specific knob, so it lives on `PerplexitySearchRequest`
12
- * rather than the cross-provider `CommonSearchRequest`.
11
+ * Provider-specific, so it lives on `PerplexitySearchRequest` rather than the
12
+ * cross-provider `CommonSearchRequest`. Mutually exclusive with the token caps
13
+ * (`max_tokens` / `max_tokens_per_page`): `/search` 500s if both are sent.
13
14
  */
14
15
  type PerplexitySearchContextSize = "low" | "medium" | "high";
15
16
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;AAYA;;;;;AAOA;;;;;AAWA;KAlBY,2BAAA;;;;;;KAOA,uBAAA;;;;;;KAWA,yBAAA"}
1
+ {"version":3,"file":"models.d.mts","names":[],"sources":["../src/models.ts"],"mappings":";;;;;;;;;;;;;;KAaY;;;;;;KAOA;;;;;;KAWA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/perplexity",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Perplexity provider for @effect-uai/core (web search).",
5
5
  "keywords": [
6
6
  "ai",
@@ -47,13 +47,13 @@
47
47
  "access": "public"
48
48
  },
49
49
  "devDependencies": {
50
- "effect": "4.0.0-beta.94",
51
- "typescript": "^6.0.3",
52
- "@effect-uai/core": "0.11.0"
50
+ "effect": "4.0.0-rc.111",
51
+ "typescript": "^7.0.2",
52
+ "@effect-uai/core": "0.12.1"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@effect-uai/core": ">=0.2.0 <1",
56
- "effect": ">=4.0.0-beta.94 <5.0.0"
56
+ "effect": ">=4.0.0-rc.111 <5.0.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsdown",
@@ -138,7 +138,6 @@ const completionToTurn = (wire: WireCompletion): Turn => {
138
138
  type: "message",
139
139
  role: "assistant",
140
140
  content: [{ type: "output_text", text, ...(annotations.length > 0 && { annotations }) }],
141
- providerData: wire,
142
141
  },
143
142
  ],
144
143
  usage: {
@@ -158,21 +157,16 @@ const jobStateOf = (wire: WireJob): ResearchState =>
158
157
  Match.value(wire.status).pipe(
159
158
  Match.when("CREATED", (): ResearchState => ({ _tag: "Pending" })),
160
159
  Match.when("IN_PROGRESS", (): ResearchState => ({ _tag: "Running" })),
161
- Match.when(
162
- "COMPLETED",
163
- (): ResearchState =>
164
- wire.response != null
165
- ? { _tag: "Succeeded", result: completionToTurn(wire.response) }
166
- : { _tag: "Failed", reason: "job completed with no response body", raw: wire },
167
- ),
168
- Match.when(
169
- "FAILED",
170
- (): ResearchState => ({
171
- _tag: "Failed",
172
- ...(wire.error_message != null && { reason: wire.error_message }),
173
- raw: wire,
174
- }),
160
+ Match.when("COMPLETED", (): ResearchState =>
161
+ wire.response != null
162
+ ? { _tag: "Succeeded", result: completionToTurn(wire.response) }
163
+ : { _tag: "Failed", reason: "job completed with no response body", raw: wire },
175
164
  ),
165
+ Match.when("FAILED", (): ResearchState => ({
166
+ _tag: "Failed",
167
+ ...(wire.error_message != null && { reason: wire.error_message }),
168
+ raw: wire,
169
+ })),
176
170
  Match.exhaustive,
177
171
  )
178
172
 
@@ -21,7 +21,11 @@ import type { PerplexitySearchContextSize } from "./models.js"
21
21
  * web directly.
22
22
  */
23
23
  export type PerplexitySearchRequest = CommonSearchRequest & {
24
- /** Depth / cost trade-off - see {@link PerplexitySearchContextSize}. */
24
+ /**
25
+ * Depth / cost trade-off for per-page extraction - see
26
+ * {@link PerplexitySearchContextSize}. Mutually exclusive with
27
+ * `maxTokensPerPage`; when both are set the token cap wins (see the codec).
28
+ */
25
29
  readonly searchContextSize?: PerplexitySearchContextSize
26
30
  /** Cap on tokens extracted per result page (1..1_000_000). */
27
31
  readonly maxTokensPerPage?: number
@@ -100,12 +104,14 @@ const buildBody = (request: PerplexitySearchRequest): WireBody => {
100
104
  ...(request.endDate !== undefined && {
101
105
  search_before_date_filter: formatMmDdYyyy(request.endDate),
102
106
  }),
103
- ...(request.searchContextSize !== undefined && {
104
- search_context_size: request.searchContextSize,
105
- }),
106
- ...(request.maxTokensPerPage !== undefined && {
107
- max_tokens_per_page: request.maxTokensPerPage,
108
- }),
107
+ // `/search` rejects `search_context_size` alongside a token cap (both govern
108
+ // per-page extraction) with a 500, so the two are mutually exclusive. The
109
+ // token cap is the finer control, so it wins when both are set.
110
+ ...(request.maxTokensPerPage !== undefined
111
+ ? { max_tokens_per_page: request.maxTokensPerPage }
112
+ : request.searchContextSize !== undefined && {
113
+ search_context_size: request.searchContextSize,
114
+ }),
109
115
  }
110
116
  }
111
117
 
package/src/models.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Perplexity's Search API takes no model identifier - unlike chat
3
- * completions, `/search` ranks the web directly. The one mode knob is the
4
- * search-context size, which trades latency / cost for depth.
2
+ * Depth / cost knob for Perplexity's Search API (`/search`), controlling how
3
+ * much content is extracted per result page. It trades latency / cost for
4
+ * depth:
5
5
  *
6
- * - `low` — fastest, fewest pages fetched.
7
- * - `medium` — balanced.
8
- * - `high` — most thorough (Perplexity's default).
6
+ * - `low`: short passages most relevant to the query.
7
+ * - `medium`: a balanced amount of content per document.
8
+ * - `high`: detailed content (Perplexity's default).
9
9
  *
10
- * It is a provider-specific knob, so it lives on `PerplexitySearchRequest`
11
- * rather than the cross-provider `CommonSearchRequest`.
10
+ * Provider-specific, so it lives on `PerplexitySearchRequest` rather than the
11
+ * cross-provider `CommonSearchRequest`. Mutually exclusive with the token caps
12
+ * (`max_tokens` / `max_tokens_per_page`): `/search` 500s if both are sent.
12
13
  */
13
14
  export type PerplexitySearchContextSize = "low" | "medium" | "high"
14
15