@oh-my-pi/pi-coding-agent 14.5.9 → 14.5.10

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.
@@ -9,14 +9,18 @@
9
9
  * and various authentication methods (bearer token, basic auth, or none).
10
10
  *
11
11
  * Configuration via settings:
12
- * searxng.endpoint - Base URL of the SearXNG instance (e.g. https://searx.example.org)
13
- * searxng.token - Optional bearer token for authentication
14
- * searxng.categories - Optional comma-separated categories filter
15
- * searxng.language - Optional language code (e.g. en, zh-CN)
12
+ * searxng.endpoint - Base URL of the SearXNG instance (e.g. https://searx.example.org)
13
+ * searxng.token - Optional bearer token for authentication
14
+ * searxng.basicUsername - Optional RFC 7617 Basic auth username
15
+ * searxng.basicPassword - Optional RFC 7617 Basic auth password
16
+ * searxng.categories - Optional comma-separated categories filter
17
+ * searxng.language - Optional language code (e.g. en, zh-CN)
16
18
  *
17
19
  * Environment variable fallbacks:
18
- * SEARXNG_ENDPOINT - Base URL of the SearXNG instance
19
- * SEARXNG_TOKEN - Optional bearer token
20
+ * SEARXNG_ENDPOINT - Base URL of the SearXNG instance
21
+ * SEARXNG_TOKEN - Optional bearer token
22
+ * SEARXNG_BASIC_USERNAME - Optional RFC 7617 Basic auth username
23
+ * SEARXNG_BASIC_PASSWORD - Optional RFC 7617 Basic auth password
20
24
  *
21
25
  * Reference: https://docs.searxng.org/dev/search_api.html
22
26
  */
@@ -61,6 +65,11 @@ interface SearXNGResponse {
61
65
  unresponsive_engines?: Array<[string, string]>;
62
66
  }
63
67
 
68
+ interface SearXNGAuth {
69
+ type: "basic" | "bearer";
70
+ value: string;
71
+ }
72
+
64
73
  /** Find SearXNG endpoint from settings or environment. */
65
74
  function findEndpoint(): string | null {
66
75
  try {
@@ -83,6 +92,53 @@ function findToken(): string | null {
83
92
  return process.env.SEARXNG_TOKEN ?? null;
84
93
  }
85
94
 
95
+ /** Find SearXNG Basic auth username from settings or environment. */
96
+ function findBasicUsername(): string | null {
97
+ try {
98
+ const username = settings.get("searxng.basicUsername");
99
+ if (username !== undefined) return username;
100
+ } catch {
101
+ // Settings not initialized yet
102
+ }
103
+ return process.env.SEARXNG_BASIC_USERNAME ?? null;
104
+ }
105
+
106
+ /** Find SearXNG Basic auth password from settings or environment. */
107
+ function findBasicPassword(): string | null {
108
+ try {
109
+ const password = settings.get("searxng.basicPassword");
110
+ if (password !== undefined) return password;
111
+ } catch {
112
+ // Settings not initialized yet
113
+ }
114
+ return process.env.SEARXNG_BASIC_PASSWORD ?? null;
115
+ }
116
+
117
+ /** Build the RFC 7617 Basic auth credential using UTF-8 bytes. */
118
+ function buildBasicAuthValue(username: string, password: string): string {
119
+ return Buffer.from(`${username}:${password}`, "utf-8").toString("base64");
120
+ }
121
+
122
+ /** Find SearXNG authentication from settings or environment. Basic auth takes precedence over bearer tokens. */
123
+ function findAuth(): SearXNGAuth | null {
124
+ const basicUsername = findBasicUsername();
125
+ const basicPassword = findBasicPassword();
126
+ if (basicUsername !== null || basicPassword !== null) {
127
+ if (basicUsername === null || basicPassword === null) {
128
+ throw new Error(
129
+ "SearXNG Basic auth requires both searxng.basicUsername and searxng.basicPassword, or SEARXNG_BASIC_USERNAME and SEARXNG_BASIC_PASSWORD.",
130
+ );
131
+ }
132
+ if (basicUsername.includes(":")) {
133
+ throw new Error("SearXNG Basic auth username cannot contain ':' because RFC 7617 uses it as the separator.");
134
+ }
135
+ return { type: "basic", value: buildBasicAuthValue(basicUsername, basicPassword) };
136
+ }
137
+
138
+ const token = findToken();
139
+ return token ? { type: "bearer", value: token } : null;
140
+ }
141
+
86
142
  /** Build the search URL and headers for a SearXNG request */
87
143
  function buildRequest(
88
144
  endpoint: string,
@@ -94,7 +150,7 @@ function buildRequest(
94
150
  language?: string;
95
151
  signal?: AbortSignal;
96
152
  },
97
- token: string | null,
153
+ auth: SearXNGAuth | null,
98
154
  ): { url: URL; headers: Record<string, string> } {
99
155
  const base = endpoint.replace(/\/+$/, "");
100
156
  const url = new URL(`${base}/search`);
@@ -122,8 +178,10 @@ function buildRequest(
122
178
  Accept: "application/json",
123
179
  };
124
180
 
125
- if (token) {
126
- headers.Authorization = `Bearer ${token}`;
181
+ if (auth?.type === "basic") {
182
+ headers.Authorization = `Basic ${auth.value}`;
183
+ } else if (auth?.type === "bearer") {
184
+ headers.Authorization = `Bearer ${auth.value}`;
127
185
  }
128
186
 
129
187
  return { url, headers };
@@ -139,9 +197,9 @@ async function callSearXNGSearch(
139
197
  language?: string;
140
198
  signal?: AbortSignal;
141
199
  },
142
- token: string | null,
200
+ auth: SearXNGAuth | null,
143
201
  ): Promise<SearXNGResponse> {
144
- const { url, headers } = buildRequest(endpoint, params, token);
202
+ const { url, headers } = buildRequest(endpoint, params, auth);
145
203
 
146
204
  const response = await fetch(url, {
147
205
  headers,
@@ -172,7 +230,7 @@ export async function searchSearXNG(params: {
172
230
  );
173
231
  }
174
232
 
175
- const token = findToken();
233
+ const auth = findAuth();
176
234
 
177
235
  let categories: string | undefined;
178
236
  let language: string | undefined;
@@ -190,7 +248,7 @@ export async function searchSearXNG(params: {
190
248
  categories,
191
249
  language,
192
250
  },
193
- token,
251
+ auth,
194
252
  );
195
253
 
196
254
  const sources: SearchSource[] = [];