@effect-x/ultimate-search 0.1.2 → 0.1.4

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.
Files changed (38) hide show
  1. package/dist/cli.js +67199 -584
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +3 -3
  4. package/src/cli.ts +21 -0
  5. package/src/commands/fetch.ts +95 -0
  6. package/src/commands/map.ts +97 -0
  7. package/src/commands/mcp/stdio.ts +27 -0
  8. package/src/commands/mcp.ts +7 -0
  9. package/src/commands/root.ts +10 -0
  10. package/src/commands/search/dual.ts +88 -0
  11. package/src/commands/search/grok.ts +70 -0
  12. package/src/commands/search/tavily.ts +99 -0
  13. package/src/commands/search.ts +9 -0
  14. package/src/config/settings.ts +261 -0
  15. package/src/providers/firecrawl/client.ts +75 -0
  16. package/src/providers/firecrawl/schema.ts +31 -0
  17. package/src/providers/grok/client.ts +77 -0
  18. package/src/providers/grok/schema.ts +107 -0
  19. package/src/providers/tavily/client.ts +143 -0
  20. package/src/providers/tavily/schema.ts +207 -0
  21. package/src/services/dual-search.ts +150 -0
  22. package/src/services/firecrawl-fetch.ts +101 -0
  23. package/src/services/grok-search.ts +68 -0
  24. package/src/services/read-only-mcp.ts +275 -0
  25. package/src/services/tavily-extract.ts +66 -0
  26. package/src/services/tavily-map.ts +38 -0
  27. package/src/services/tavily-search.ts +40 -0
  28. package/src/services/web-fetch-schema.ts +72 -0
  29. package/src/services/web-fetch.ts +100 -0
  30. package/src/shared/cli-flags.ts +25 -0
  31. package/src/shared/command-output.ts +21 -0
  32. package/src/shared/effect.ts +52 -0
  33. package/src/shared/errors.ts +56 -0
  34. package/src/shared/output.ts +210 -0
  35. package/src/shared/provider-http-client.ts +73 -0
  36. package/src/shared/render-error.ts +101 -0
  37. package/src/shared/schema.ts +42 -0
  38. package/src/shared/tracing.ts +53 -0
@@ -0,0 +1,261 @@
1
+ import { Config, Effect, Layer, Option, ServiceMap } from "effect";
2
+ import { ConfigValidationError, type UltimateSearchError } from "../shared/errors";
3
+ import type { ServicesReturns } from "../shared/effect";
4
+ import {
5
+ optionalAbsoluteUrlStringFromStringSchema,
6
+ optionalTrimmedNonEmptyStringFromStringSchema,
7
+ } from "../shared/schema";
8
+
9
+ export interface ProviderEnvironment {
10
+ readonly apiUrl: Option.Option<string>;
11
+ readonly apiKey: Option.Option<string>;
12
+ }
13
+
14
+ export interface GrokEnvironment extends ProviderEnvironment {
15
+ readonly model: string;
16
+ }
17
+
18
+ export interface FirecrawlEnvironment {
19
+ readonly apiUrl: string;
20
+ readonly apiKey: Option.Option<string>;
21
+ }
22
+
23
+ export interface UltimateSearchSettings {
24
+ readonly grok: GrokEnvironment;
25
+ readonly tavily: ProviderEnvironment;
26
+ readonly firecrawl: FirecrawlEnvironment;
27
+ }
28
+
29
+ export interface GrokProviderConfig {
30
+ readonly apiUrl: string;
31
+ readonly apiKey: string;
32
+ readonly model: string;
33
+ }
34
+
35
+ export interface TavilyProviderConfig {
36
+ readonly apiUrl: string;
37
+ readonly apiKey: string;
38
+ }
39
+
40
+ export interface FirecrawlProviderConfig {
41
+ readonly apiUrl: string;
42
+ readonly apiKey: string;
43
+ }
44
+
45
+ export class UltimateSearchConfig extends ServiceMap.Service<
46
+ UltimateSearchConfig,
47
+ {
48
+ readonly settings: UltimateSearchSettings;
49
+ readonly getGrokConfig: () => Effect.Effect<GrokProviderConfig, UltimateSearchError>;
50
+ readonly getTavilyConfig: () => Effect.Effect<TavilyProviderConfig, UltimateSearchError>;
51
+ readonly getFirecrawlConfig: () => Effect.Effect<FirecrawlProviderConfig, UltimateSearchError>;
52
+ }
53
+ >()("UltimateSearchConfig") {
54
+ static readonly layer = Layer.effect(
55
+ UltimateSearchConfig,
56
+ Effect.gen(function* () {
57
+ const settings: UltimateSearchConfig.Methods["settings"] = yield* loadSettings.pipe(
58
+ Effect.withSpan("UltimateSearchConfig.settings"),
59
+ );
60
+
61
+ const getGrokConfig: UltimateSearchConfig.Methods["getGrokConfig"] = Effect.fn(
62
+ "UltimateSearchConfig.getGrokConfig",
63
+ )(function* (): Effect.fn.Return<GrokProviderConfig, ConfigValidationError, never> {
64
+ const grok = yield* strictConfigEffect(grokEnvironmentConfig);
65
+ const details: Array<string> = [];
66
+
67
+ if (Option.isNone(grok.apiUrl)) {
68
+ details.push("Set GROK_API_URL to the grok base URL.");
69
+ }
70
+
71
+ if (Option.isNone(grok.apiKey)) {
72
+ details.push("Set GROK_API_KEY to the grok bearer token.");
73
+ }
74
+
75
+ if (details.length > 0) {
76
+ return yield* new ConfigValidationError({
77
+ provider: "grok",
78
+ message: "Missing required Grok configuration.",
79
+ details,
80
+ });
81
+ }
82
+
83
+ return {
84
+ apiUrl: Option.getOrElse(grok.apiUrl, () => ""),
85
+ apiKey: Option.getOrElse(grok.apiKey, () => ""),
86
+ model: grok.model,
87
+ } satisfies GrokProviderConfig;
88
+ });
89
+
90
+ const getTavilyConfig: UltimateSearchConfig.Methods["getTavilyConfig"] = Effect.fn(
91
+ "UltimateSearchConfig.getTavilyConfig",
92
+ )(function* (): Effect.fn.Return<TavilyProviderConfig, ConfigValidationError, never> {
93
+ const tavily = yield* strictConfigEffect(tavilyEnvironmentConfig);
94
+ const details: Array<string> = [];
95
+
96
+ if (Option.isNone(tavily.apiUrl)) {
97
+ details.push("Set TAVILY_API_URL to the Tavily or Tavily proxy base URL.");
98
+ }
99
+
100
+ if (Option.isNone(tavily.apiKey)) {
101
+ details.push("Set TAVILY_API_KEY to the Tavily or Tavily proxy bearer token.");
102
+ }
103
+
104
+ if (details.length > 0) {
105
+ return yield* new ConfigValidationError({
106
+ provider: "tavily",
107
+ message: "Missing required Tavily configuration.",
108
+ details,
109
+ });
110
+ }
111
+
112
+ return {
113
+ apiUrl: Option.getOrElse(tavily.apiUrl, () => ""),
114
+ apiKey: Option.getOrElse(tavily.apiKey, () => ""),
115
+ } satisfies TavilyProviderConfig;
116
+ });
117
+
118
+ const getFirecrawlConfig: UltimateSearchConfig.Methods["getFirecrawlConfig"] = Effect.fn(
119
+ "UltimateSearchConfig.getFirecrawlConfig",
120
+ )(function* (): Effect.fn.Return<FirecrawlProviderConfig, ConfigValidationError, never> {
121
+ const firecrawl = yield* strictConfigEffect(firecrawlEnvironmentConfig);
122
+
123
+ if (Option.isNone(firecrawl.apiKey)) {
124
+ return yield* new ConfigValidationError({
125
+ provider: "firecrawl",
126
+ message: "Missing required FireCrawl configuration.",
127
+ details: ["Set FIRECRAWL_API_KEY to the FireCrawl bearer token."],
128
+ });
129
+ }
130
+
131
+ return {
132
+ apiUrl: firecrawl.apiUrl,
133
+ apiKey: Option.getOrElse(firecrawl.apiKey, () => ""),
134
+ } satisfies FirecrawlProviderConfig;
135
+ });
136
+
137
+ return UltimateSearchConfig.of({
138
+ settings,
139
+ getGrokConfig,
140
+ getTavilyConfig,
141
+ getFirecrawlConfig,
142
+ });
143
+ }),
144
+ );
145
+ }
146
+
147
+ export declare namespace UltimateSearchConfig {
148
+ export type Methods = ServiceMap.Service.Shape<typeof UltimateSearchConfig>;
149
+ export type Returns<key extends keyof Methods, R = never> = ServicesReturns<Methods[key], R>;
150
+ }
151
+
152
+ const optionalUrlConfig = (name: string) =>
153
+ Config.schema(
154
+ optionalAbsoluteUrlStringFromStringSchema(`${name} must be an absolute URL.`),
155
+ name,
156
+ ).pipe(Config.withDefault(Option.none<string>()));
157
+
158
+ const optionalSecretConfig = (name: string) =>
159
+ Config.schema(optionalTrimmedNonEmptyStringFromStringSchema, name).pipe(
160
+ Config.withDefault(Option.none<string>()),
161
+ );
162
+
163
+ const requiredTextConfig = (name: string, fallback: string) =>
164
+ Config.schema(optionalTrimmedNonEmptyStringFromStringSchema, name).pipe(
165
+ Config.withDefault(Option.none<string>()),
166
+ Config.map(Option.getOrElse(() => fallback)),
167
+ );
168
+
169
+ const requiredUrlConfig = (name: string, fallback: string) =>
170
+ Config.schema(
171
+ optionalAbsoluteUrlStringFromStringSchema(`${name} must be an absolute URL.`),
172
+ name,
173
+ ).pipe(Config.withDefault(Option.none<string>()), Config.map(Option.getOrElse(() => fallback)));
174
+
175
+ const grokEnvironmentConfig = Config.all({
176
+ apiUrl: optionalUrlConfig("GROK_API_URL"),
177
+ apiKey: optionalSecretConfig("GROK_API_KEY"),
178
+ model: requiredTextConfig("GROK_MODEL", "grok-4.1-fast"),
179
+ }) satisfies Config.Config<GrokEnvironment>;
180
+
181
+ const tavilyEnvironmentConfig = Config.all({
182
+ apiUrl: optionalUrlConfig("TAVILY_API_URL"),
183
+ apiKey: optionalSecretConfig("TAVILY_API_KEY"),
184
+ }) satisfies Config.Config<ProviderEnvironment>;
185
+
186
+ const firecrawlEnvironmentConfig = Config.all({
187
+ apiUrl: requiredUrlConfig("FIRECRAWL_API_URL", "https://api.firecrawl.dev/v2"),
188
+ apiKey: optionalSecretConfig("FIRECRAWL_API_KEY"),
189
+ }) satisfies Config.Config<FirecrawlEnvironment>;
190
+
191
+ const defaultGrokEnvironment: GrokEnvironment = {
192
+ apiUrl: Option.none(),
193
+ apiKey: Option.none(),
194
+ model: "grok-4.1-fast",
195
+ };
196
+
197
+ const defaultTavilyEnvironment: ProviderEnvironment = {
198
+ apiUrl: Option.none(),
199
+ apiKey: Option.none(),
200
+ };
201
+
202
+ const defaultFirecrawlEnvironment: FirecrawlEnvironment = {
203
+ apiUrl: "https://api.firecrawl.dev/v2",
204
+ apiKey: Option.none(),
205
+ };
206
+
207
+ const mapConfigLoadError = (error: unknown) =>
208
+ new ConfigValidationError({
209
+ provider: "shared",
210
+ message: "Failed to load CLI configuration.",
211
+ details: configErrorDetails(error),
212
+ cause: error,
213
+ });
214
+
215
+ const strictConfigEffect = <A>(config: Config.Config<A>) =>
216
+ config
217
+ .asEffect()
218
+ .pipe(Effect.mapError(mapConfigLoadError), Effect.withSpan("UltimateSearchConfig.settings"));
219
+
220
+ const bestEffortConfigEffect = <A>(config: Config.Config<A>, fallback: A) =>
221
+ strictConfigEffect(config).pipe(Effect.catch(() => Effect.succeed(fallback)));
222
+
223
+ const loadSettings = Effect.all({
224
+ grok: bestEffortConfigEffect(grokEnvironmentConfig, defaultGrokEnvironment),
225
+ tavily: bestEffortConfigEffect(tavilyEnvironmentConfig, defaultTavilyEnvironment),
226
+ firecrawl: bestEffortConfigEffect(firecrawlEnvironmentConfig, defaultFirecrawlEnvironment),
227
+ }) satisfies Effect.Effect<UltimateSearchSettings, never, never>;
228
+
229
+ const configErrorDetails = (error: unknown): Array<string> => {
230
+ if (error instanceof Config.ConfigError && error.message.length > 0) {
231
+ return [error.message];
232
+ }
233
+
234
+ if (error instanceof Error && error.message.length > 0) {
235
+ return [error.message];
236
+ }
237
+
238
+ if (
239
+ typeof error === "object" &&
240
+ error !== null &&
241
+ "message" in error &&
242
+ typeof error.message === "string" &&
243
+ error.message.length > 0
244
+ ) {
245
+ return [error.message];
246
+ }
247
+
248
+ if (typeof error === "string" && error.length > 0) {
249
+ return [error];
250
+ }
251
+
252
+ if (error != null) {
253
+ const text = String(error);
254
+
255
+ if (text.length > 0 && text !== "[object Object]") {
256
+ return [text];
257
+ }
258
+ }
259
+
260
+ return [];
261
+ };
@@ -0,0 +1,75 @@
1
+ import { Effect, Layer, Schema, ServiceMap } from "effect";
2
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
3
+ import { UltimateSearchConfig } from "../../config/settings";
4
+ import type { ServicesReturns } from "../../shared/effect";
5
+ import { ProviderDecodeError, type UltimateSearchError } from "../../shared/errors";
6
+ import {
7
+ catchProviderHttpError,
8
+ decodeJsonResponse,
9
+ makeProviderHttpClient,
10
+ } from "../../shared/provider-http-client";
11
+ import {
12
+ type FirecrawlScrapeRequest,
13
+ FirecrawlScrapeResponseSchema,
14
+ type FirecrawlScrapeResponse,
15
+ } from "./schema";
16
+
17
+ const decodeFirecrawlScrapeResponse = Schema.decodeUnknownEffect(FirecrawlScrapeResponseSchema);
18
+
19
+ const mapDecodeError = (error: unknown, fallback: string) =>
20
+ new ProviderDecodeError({
21
+ provider: "firecrawl",
22
+ message: error instanceof Error ? error.message : fallback,
23
+ cause: error,
24
+ });
25
+
26
+ export class FirecrawlProviderClient extends ServiceMap.Service<
27
+ FirecrawlProviderClient,
28
+ {
29
+ readonly scrape: (
30
+ request: FirecrawlScrapeRequest,
31
+ ) => Effect.Effect<FirecrawlScrapeResponse, UltimateSearchError, never>;
32
+ }
33
+ >()("FirecrawlProviderClient") {
34
+ static readonly layer = Layer.effect(
35
+ FirecrawlProviderClient,
36
+ Effect.gen(function* () {
37
+ const config = yield* UltimateSearchConfig;
38
+ const http = makeProviderHttpClient(yield* HttpClient.HttpClient);
39
+
40
+ const scrape: FirecrawlProviderClient.Methods["scrape"] = Effect.fn(
41
+ "FirecrawlProviderClient.scrape",
42
+ )(function* (payload): FirecrawlProviderClient.Returns<"scrape"> {
43
+ const firecrawl = yield* config.getFirecrawlConfig();
44
+ const request = HttpClientRequest.post(`${firecrawl.apiUrl}/scrape`).pipe(
45
+ HttpClientRequest.acceptJson,
46
+ HttpClientRequest.bearerToken(firecrawl.apiKey),
47
+ HttpClientRequest.bodyJsonUnsafe(payload),
48
+ );
49
+
50
+ const response = yield* http
51
+ .execute(request)
52
+ .pipe(
53
+ catchProviderHttpError(
54
+ "firecrawl",
55
+ "Failed to send the FireCrawl request.",
56
+ (status: number) => `FireCrawl returned HTTP ${status}.`,
57
+ ),
58
+ );
59
+
60
+ return yield* decodeJsonResponse(response, decodeFirecrawlScrapeResponse, (error) =>
61
+ mapDecodeError(error, "Failed to decode the FireCrawl response payload."),
62
+ );
63
+ });
64
+
65
+ return FirecrawlProviderClient.of({
66
+ scrape,
67
+ });
68
+ }),
69
+ );
70
+ }
71
+
72
+ export declare namespace FirecrawlProviderClient {
73
+ export type Methods = ServiceMap.Service.Shape<typeof FirecrawlProviderClient>;
74
+ export type Returns<key extends keyof Methods, R = never> = ServicesReturns<Methods[key], R>;
75
+ }
@@ -0,0 +1,31 @@
1
+ import { Schema } from "effect";
2
+
3
+ export const FirecrawlFormatSchema = Schema.Literals(["markdown"] as const);
4
+
5
+ export type FirecrawlFormat = typeof FirecrawlFormatSchema.Type;
6
+
7
+ export const FirecrawlScrapeRequestSchema = Schema.Struct({
8
+ url: Schema.String,
9
+ formats: Schema.NonEmptyArray(FirecrawlFormatSchema),
10
+ });
11
+
12
+ export type FirecrawlScrapeRequest = typeof FirecrawlScrapeRequestSchema.Type;
13
+
14
+ export const FirecrawlScrapeResponseSchema = Schema.Struct({
15
+ success: Schema.optional(Schema.Boolean),
16
+ data: Schema.optional(
17
+ Schema.NullOr(
18
+ Schema.Struct({
19
+ markdown: Schema.optional(Schema.NullOr(Schema.String)),
20
+ content: Schema.optional(Schema.NullOr(Schema.String)),
21
+ metadata: Schema.optional(
22
+ Schema.Struct({
23
+ title: Schema.optional(Schema.NullOr(Schema.String)),
24
+ }),
25
+ ),
26
+ }),
27
+ ),
28
+ ),
29
+ });
30
+
31
+ export type FirecrawlScrapeResponse = typeof FirecrawlScrapeResponseSchema.Type;
@@ -0,0 +1,77 @@
1
+ import { Effect, Layer, Schema, ServiceMap } from "effect";
2
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
3
+ import { UltimateSearchConfig } from "../../config/settings";
4
+ import type { ServicesReturns } from "../../shared/effect";
5
+ import { ProviderDecodeError, type UltimateSearchError } from "../../shared/errors";
6
+ import {
7
+ catchProviderHttpError,
8
+ decodeJsonResponse,
9
+ makeProviderHttpClient,
10
+ } from "../../shared/provider-http-client";
11
+ import {
12
+ type GrokChatCompletionRequest,
13
+ GrokChatCompletionResponseSchema,
14
+ type GrokChatCompletionResponse,
15
+ } from "./schema";
16
+
17
+ const decodeGrokChatCompletionResponse = Schema.decodeUnknownEffect(
18
+ GrokChatCompletionResponseSchema,
19
+ );
20
+
21
+ const mapDecodeError = (error: unknown, fallback: string) =>
22
+ new ProviderDecodeError({
23
+ provider: "grok",
24
+ message: error instanceof Error ? error.message : fallback,
25
+ cause: error,
26
+ });
27
+
28
+ export class GrokProviderClient extends ServiceMap.Service<
29
+ GrokProviderClient,
30
+ {
31
+ readonly createChatCompletion: (
32
+ request: GrokChatCompletionRequest,
33
+ ) => Effect.Effect<GrokChatCompletionResponse, UltimateSearchError, never>;
34
+ }
35
+ >()("GrokProviderClient") {
36
+ static readonly layer = Layer.effect(
37
+ GrokProviderClient,
38
+ Effect.gen(function* () {
39
+ const config = yield* UltimateSearchConfig;
40
+ const http = makeProviderHttpClient(yield* HttpClient.HttpClient);
41
+
42
+ const createChatCompletion: GrokProviderClient.Methods["createChatCompletion"] = Effect.fn(
43
+ "GrokProviderClient.createChatCompletion",
44
+ )(function* (payload): GrokProviderClient.Returns<"createChatCompletion"> {
45
+ const grok = yield* config.getGrokConfig();
46
+ const request = HttpClientRequest.post(`${grok.apiUrl}/v1/chat/completions`).pipe(
47
+ HttpClientRequest.acceptJson,
48
+ HttpClientRequest.bearerToken(grok.apiKey),
49
+ HttpClientRequest.bodyJsonUnsafe(payload),
50
+ );
51
+
52
+ const response = yield* http
53
+ .execute(request)
54
+ .pipe(
55
+ catchProviderHttpError(
56
+ "grok",
57
+ "Failed to send the Grok request.",
58
+ (status: number) => `Grok returned HTTP ${status}.`,
59
+ ),
60
+ );
61
+
62
+ return yield* decodeJsonResponse(response, decodeGrokChatCompletionResponse, (error) =>
63
+ mapDecodeError(error, "Failed to decode the Grok response payload."),
64
+ );
65
+ });
66
+
67
+ return GrokProviderClient.of({
68
+ createChatCompletion,
69
+ });
70
+ }),
71
+ );
72
+ }
73
+
74
+ export declare namespace GrokProviderClient {
75
+ export type Methods = ServiceMap.Service.Shape<typeof GrokProviderClient>;
76
+ export type Returns<key extends keyof Methods, R = never> = ServicesReturns<Methods[key], R>;
77
+ }
@@ -0,0 +1,107 @@
1
+ import { Option, Schema } from "effect";
2
+ import { trimmedNonEmptyStringSchema } from "../../shared/schema";
3
+
4
+ export const GrokUsageSchema = Schema.Struct({
5
+ prompt_tokens: Schema.Number,
6
+ completion_tokens: Schema.Number,
7
+ total_tokens: Schema.Number,
8
+ });
9
+
10
+ export type GrokUsage = typeof GrokUsageSchema.Type;
11
+
12
+ export const GrokMessageSchema = Schema.Struct({
13
+ role: Schema.Literals(["system", "user", "assistant"] as const),
14
+ content: Schema.String,
15
+ });
16
+
17
+ export type GrokMessage = typeof GrokMessageSchema.Type;
18
+
19
+ export const GrokChatCompletionRequestSchema = Schema.Struct({
20
+ model: Schema.NonEmptyString,
21
+ stream: Schema.Boolean,
22
+ messages: Schema.NonEmptyArray(GrokMessageSchema),
23
+ });
24
+
25
+ export type GrokChatCompletionRequest = typeof GrokChatCompletionRequestSchema.Type;
26
+
27
+ export const GrokChatCompletionResponseSchema = Schema.Struct({
28
+ model: Schema.String,
29
+ choices: Schema.NonEmptyArray(
30
+ Schema.Struct({
31
+ message: GrokMessageSchema,
32
+ }),
33
+ ),
34
+ usage: GrokUsageSchema,
35
+ });
36
+
37
+ export type GrokChatCompletionResponse = typeof GrokChatCompletionResponseSchema.Type;
38
+
39
+ export class GrokSearchInput extends Schema.Class<GrokSearchInput>("GrokSearchInput")({
40
+ query: trimmedNonEmptyStringSchema("query must be a non-empty string"),
41
+ platform: Schema.Option(Schema.NonEmptyString),
42
+ model: Schema.Option(Schema.NonEmptyString),
43
+ }) {
44
+ static decodeEffect = Schema.decodeUnknownEffect(GrokSearchInput);
45
+ }
46
+
47
+ export interface GrokSearchResult {
48
+ readonly content: string;
49
+ readonly model: string;
50
+ readonly usage: GrokUsage;
51
+ }
52
+
53
+ export const GrokSearchResultSchema = Schema.Struct({
54
+ content: Schema.String,
55
+ model: Schema.String,
56
+ usage: GrokUsageSchema,
57
+ });
58
+
59
+ const timeSensitivePattern =
60
+ /今天|最新|当前|latest|recent|today|current|now|这几天|本周|本月|近期|最近/iu;
61
+
62
+ export const grokSystemPrompt = `# Core Instruction
63
+
64
+ 1. User needs may be vague. Think divergently, infer intent from multiple angles, and leverage full conversation context to progressively clarify their true needs.
65
+ 2. **Breadth-First Search**—Approach problems from multiple dimensions. Brainstorm 5+ perspectives and execute parallel searches for each. Consult as many high-quality sources as possible before responding.
66
+ 3. **Depth-First Search**—After broad exploration, select ≥2 most relevant perspectives for deep investigation into specialized knowledge.
67
+ 4. **Evidence-Based Reasoning & Traceable Sources**—Every claim must be followed by a citation. More credible sources strengthen arguments. If no references exist, remain silent.
68
+ 5. Before responding, ensure full execution of Steps 1–4.
69
+
70
+ # Search Instruction
71
+
72
+ 1. Think carefully before responding—anticipate the user's true intent to ensure precision.
73
+ 2. Verify every claim rigorously to avoid misinformation.
74
+ 3. Follow problem logic—dig deeper until clues are exhaustively clear. Use multiple parallel tool calls per query and ensure answers are well-sourced.
75
+ 4. Search in English first (prioritizing English resources for volume/quality), but switch to Chinese if context demands.
76
+ 5. Prioritize authoritative sources: Wikipedia, academic databases, books, reputable media/journalism.
77
+ 6. Favor sharing in-depth, specialized knowledge over generic or common-sense content.
78
+
79
+ # Output Style
80
+
81
+ 1. Lead with the **most probable solution** before detailed analysis.
82
+ 2. **Define every technical term** in plain language.
83
+ 3. **Respect facts and search results—use statistical rigor to discern truth**.
84
+ 4. **Every sentence must cite sources**. More references = stronger credibility.
85
+ 5. **Strictly format outputs in polished Markdown**.`;
86
+
87
+ const withCurrentTimeContext = (query: string) => {
88
+ if (!timeSensitivePattern.test(query)) {
89
+ return query;
90
+ }
91
+
92
+ return `[Current date and time: ${new Date().toISOString()}]
93
+
94
+ ${query}`;
95
+ };
96
+
97
+ export const buildGrokUserMessage = (input: GrokSearchInput) => {
98
+ const baseMessage = withCurrentTimeContext(input.query);
99
+
100
+ return Option.match(input.platform, {
101
+ onNone: () => baseMessage,
102
+ onSome: (platform) =>
103
+ `${baseMessage}
104
+
105
+ You should focus on these platform: ${platform}`,
106
+ });
107
+ };
@@ -0,0 +1,143 @@
1
+ import { Effect, Layer, Schema, ServiceMap } from "effect";
2
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
3
+ import { UltimateSearchConfig } from "../../config/settings";
4
+ import type { ServicesReturns } from "../../shared/effect";
5
+ import { ProviderDecodeError, type UltimateSearchError } from "../../shared/errors";
6
+ import {
7
+ catchProviderHttpError,
8
+ decodeJsonResponse,
9
+ makeProviderHttpClient,
10
+ } from "../../shared/provider-http-client";
11
+ import {
12
+ type TavilyMapRequest,
13
+ TavilyMapResponseSchema,
14
+ type TavilyMapResponse,
15
+ type TavilyExtractRequest,
16
+ TavilyExtractResponseSchema,
17
+ type TavilyExtractResponse,
18
+ type TavilySearchRequest,
19
+ TavilySearchResponseSchema,
20
+ type TavilySearchResponse,
21
+ } from "./schema";
22
+
23
+ const decodeTavilySearchResponse = Schema.decodeUnknownEffect(TavilySearchResponseSchema);
24
+
25
+ const decodeTavilyMapResponse = Schema.decodeUnknownEffect(TavilyMapResponseSchema);
26
+
27
+ const decodeTavilyExtractResponse = Schema.decodeUnknownEffect(TavilyExtractResponseSchema);
28
+
29
+ const mapDecodeError = (error: unknown, fallback: string) =>
30
+ new ProviderDecodeError({
31
+ provider: "tavily",
32
+ message: error instanceof Error ? error.message : fallback,
33
+ cause: error,
34
+ });
35
+
36
+ export class TavilyProviderClient extends ServiceMap.Service<
37
+ TavilyProviderClient,
38
+ {
39
+ readonly search: (
40
+ request: TavilySearchRequest,
41
+ ) => Effect.Effect<TavilySearchResponse, UltimateSearchError, never>;
42
+ readonly map: (
43
+ request: TavilyMapRequest,
44
+ ) => Effect.Effect<TavilyMapResponse, UltimateSearchError, never>;
45
+ readonly extract: (
46
+ request: TavilyExtractRequest,
47
+ ) => Effect.Effect<TavilyExtractResponse, UltimateSearchError, never>;
48
+ }
49
+ >()("TavilyProviderClient") {
50
+ static readonly layer = Layer.effect(
51
+ TavilyProviderClient,
52
+ Effect.gen(function* () {
53
+ const config = yield* UltimateSearchConfig;
54
+ const http = makeProviderHttpClient(yield* HttpClient.HttpClient);
55
+
56
+ const search: TavilyProviderClient.Methods["search"] = Effect.fn(
57
+ "TavilyProviderClient.search",
58
+ )(function* (payload): TavilyProviderClient.Returns<"search"> {
59
+ const tavily = yield* config.getTavilyConfig();
60
+ const request = HttpClientRequest.post(`${tavily.apiUrl}/search`).pipe(
61
+ HttpClientRequest.acceptJson,
62
+ HttpClientRequest.bearerToken(tavily.apiKey),
63
+ HttpClientRequest.bodyJsonUnsafe(payload),
64
+ );
65
+
66
+ const response = yield* http
67
+ .execute(request)
68
+ .pipe(
69
+ catchProviderHttpError(
70
+ "tavily",
71
+ "Failed to send the Tavily request.",
72
+ (status: number) => `Tavily returned HTTP ${status}.`,
73
+ ),
74
+ );
75
+
76
+ return yield* decodeJsonResponse(response, decodeTavilySearchResponse, (error) =>
77
+ mapDecodeError(error, "Failed to decode the Tavily response payload."),
78
+ );
79
+ });
80
+
81
+ const map: TavilyProviderClient.Methods["map"] = Effect.fn("TavilyProviderClient.map")(
82
+ function* (payload): TavilyProviderClient.Returns<"map"> {
83
+ const tavily = yield* config.getTavilyConfig();
84
+ const request = HttpClientRequest.post(`${tavily.apiUrl}/map`).pipe(
85
+ HttpClientRequest.acceptJson,
86
+ HttpClientRequest.bearerToken(tavily.apiKey),
87
+ HttpClientRequest.bodyJsonUnsafe(payload),
88
+ );
89
+
90
+ const response = yield* http
91
+ .execute(request)
92
+ .pipe(
93
+ catchProviderHttpError(
94
+ "tavily",
95
+ "Failed to send the Tavily map request.",
96
+ (status: number) => `Tavily map returned HTTP ${status}.`,
97
+ ),
98
+ );
99
+
100
+ return yield* decodeJsonResponse(response, decodeTavilyMapResponse, (error) =>
101
+ mapDecodeError(error, "Failed to decode the Tavily map response payload."),
102
+ );
103
+ },
104
+ );
105
+
106
+ const extract: TavilyProviderClient.Methods["extract"] = Effect.fn(
107
+ "TavilyProviderClient.extract",
108
+ )(function* (payload): TavilyProviderClient.Returns<"extract"> {
109
+ const tavily = yield* config.getTavilyConfig();
110
+ const request = HttpClientRequest.post(`${tavily.apiUrl}/extract`).pipe(
111
+ HttpClientRequest.acceptJson,
112
+ HttpClientRequest.bearerToken(tavily.apiKey),
113
+ HttpClientRequest.bodyJsonUnsafe(payload),
114
+ );
115
+
116
+ const response = yield* http
117
+ .execute(request)
118
+ .pipe(
119
+ catchProviderHttpError(
120
+ "tavily",
121
+ "Failed to send the Tavily extract request.",
122
+ (status: number) => `Tavily returned HTTP ${status}.`,
123
+ ),
124
+ );
125
+
126
+ return yield* decodeJsonResponse(response, decodeTavilyExtractResponse, (error) =>
127
+ mapDecodeError(error, "Failed to decode the Tavily extract payload."),
128
+ );
129
+ });
130
+
131
+ return TavilyProviderClient.of({
132
+ map,
133
+ search,
134
+ extract,
135
+ });
136
+ }),
137
+ );
138
+ }
139
+
140
+ export declare namespace TavilyProviderClient {
141
+ export type Methods = ServiceMap.Service.Shape<typeof TavilyProviderClient>;
142
+ export type Returns<key extends keyof Methods, R = never> = ServicesReturns<Methods[key], R>;
143
+ }