@effect-uai/perplexity 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.
|
@@ -1 +1 @@
|
|
|
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;;;
|
|
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;;;cA6MR,OAAI,KACV,WACJ,OAAO,OAAO,sCAAsC,WAAW;;;;;;cAQrD,QAAK,KACX,WACJ,MAAM,MAAM,yBAAyB,qBAAqB,WAAW"}
|
|
@@ -53,6 +53,7 @@ const blockText = Match.type().pipe(Match.discriminatorsExhaustive("type")({
|
|
|
53
53
|
input_text: (b) => b.text,
|
|
54
54
|
output_text: (b) => b.text,
|
|
55
55
|
input_image: () => "",
|
|
56
|
+
output_image: () => "",
|
|
56
57
|
refusal: () => ""
|
|
57
58
|
}));
|
|
58
59
|
const historyToMessages = (history) => history.filter((i) => i.type === "message").map((m) => ({
|
|
@@ -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 },\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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"}
|
|
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 output_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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-uai/perplexity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Perplexity provider for @effect-uai/core (web search).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"effect": "4.0.0-rc.111",
|
|
51
51
|
"typescript": "^7.0.2",
|
|
52
|
-
"@effect-uai/core": "0.
|
|
52
|
+
"@effect-uai/core": "0.14.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@effect-uai/core": ">=0.2.0 <1",
|