@iflow-ai/search-openapi 0.1.0-pre.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.
- package/LICENSE +21 -0
- package/README.md +248 -0
- package/dist/auth.d.ts +22 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +68 -0
- package/dist/auth.js.map +1 -0
- package/dist/bin.d.ts +24 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +84 -0
- package/dist/bin.js.map +1 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +70 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +49 -0
- package/dist/errors.js.map +1 -0
- package/dist/handlers/image-search.d.ts +8 -0
- package/dist/handlers/image-search.d.ts.map +1 -0
- package/dist/handlers/image-search.js +53 -0
- package/dist/handlers/image-search.js.map +1 -0
- package/dist/handlers/index.d.ts +13 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +15 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/types.d.ts +37 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/types.js +13 -0
- package/dist/handlers/types.js.map +1 -0
- package/dist/handlers/web-fetch.d.ts +8 -0
- package/dist/handlers/web-fetch.d.ts.map +1 -0
- package/dist/handlers/web-fetch.js +41 -0
- package/dist/handlers/web-fetch.js.map +1 -0
- package/dist/handlers/web-search.d.ts +10 -0
- package/dist/handlers/web-search.d.ts.map +1 -0
- package/dist/handlers/web-search.js +55 -0
- package/dist/handlers/web-search.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/openapi.d.ts +19 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +112 -0
- package/dist/openapi.js.map +1 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +150 -0
- package/dist/server.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 iFlow AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# @iflow-ai/search-openapi
|
|
2
|
+
|
|
3
|
+
> HTTP / OpenAPI tool server for [iFlow Search](https://platform.iflow.cn) — exposes web search, image search, and web fetch as JSON-over-HTTP endpoints for **Open WebUI**, **Coze**, and other agent platforms that consume an OpenAPI 3.x document.
|
|
4
|
+
|
|
5
|
+
Built on [`@iflow-ai/search-core`](../search-core). Same three tools as
|
|
6
|
+
[`@iflow-ai/search-langchain`](../search-langchain) and
|
|
7
|
+
[`@iflow-ai/search-mcp`](../search-mcp) — same names, same shapes, same
|
|
8
|
+
attribution — so prompts that drive an iFlow-search-tool agent under one
|
|
9
|
+
runtime keep working verbatim under another.
|
|
10
|
+
|
|
11
|
+
## Status — MVP (`0.1.0-pre.0`)
|
|
12
|
+
|
|
13
|
+
- **Transport:** HTTP only. Plain JSON request / response. No SSE, no
|
|
14
|
+
WebSocket. The MVP uses Node's built-in `http` module — no Express,
|
|
15
|
+
Fastify, or Koa dependency.
|
|
16
|
+
- **Endpoints:**
|
|
17
|
+
- `GET /health` — liveness probe. Never gated by the bearer guard.
|
|
18
|
+
- `GET /openapi.json` — OpenAPI 3.1 document for tool catalogs.
|
|
19
|
+
- `POST /tools/iflow_web_search`
|
|
20
|
+
- `POST /tools/iflow_image_search`
|
|
21
|
+
- `POST /tools/iflow_web_fetch`
|
|
22
|
+
- **Configuration:** environment variables only, supplied by your
|
|
23
|
+
deployment (Docker `-e`, systemd `Environment=`, your platform's
|
|
24
|
+
secret manager). The process never reads from disk.
|
|
25
|
+
|
|
26
|
+
## Install & run
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# One-shot, no install — pulls the published @next tag:
|
|
30
|
+
IFLOW_API_KEY=YOUR_IFLOW_API_KEY \
|
|
31
|
+
npx -y @iflow-ai/search-openapi@next
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or pin it as a dependency:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @iflow-ai/search-openapi
|
|
38
|
+
# or
|
|
39
|
+
npm install @iflow-ai/search-openapi
|
|
40
|
+
|
|
41
|
+
iflow-search-openapi
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Node ≥ 18. The server binds to `0.0.0.0:8787` by default; override with
|
|
45
|
+
`PORT`.
|
|
46
|
+
|
|
47
|
+
> **Do not commit a real key.** Use `YOUR_IFLOW_API_KEY` everywhere you'd
|
|
48
|
+
> normally write the value and inject the real one at runtime via your
|
|
49
|
+
> platform's secret store. `@iflow-ai/search-openapi` never reads from
|
|
50
|
+
> disk and will not pick up a `.env` automatically.
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
All configuration is read from `process.env`.
|
|
55
|
+
|
|
56
|
+
| Variable | Required | Default | Purpose |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `IFLOW_API_KEY` | yes | — | Bearer token sent to iFlow as `Authorization: Bearer ...`. |
|
|
59
|
+
| `IFLOW_BASE_URL` | no | `https://platform.iflow.cn` | Override for testing / private deployments. |
|
|
60
|
+
| `IFLOW_TIMEOUT_MS` | no | `30000` | Per-request timeout in ms. Must be a positive integer if set. |
|
|
61
|
+
| `PORT` | no | `8787` | TCP port to listen on. Must be an integer in `[0, 65535]`. |
|
|
62
|
+
| `IFLOW_OPENAPI_AUTH_TOKEN` | no | — | When set, every endpoint **except** `/health` requires `Authorization: Bearer <token>`. Constant-time compared. Absent = open mode (no auth gate). |
|
|
63
|
+
| `IFLOW_OPENAPI_CLIENT` | no | — | Identifies the host platform (`open-webui`, `coze`, …). Allowed: `[a-z0-9._-]{1,64}`. Captured into the startup banner; not forwarded to iFlow today. |
|
|
64
|
+
|
|
65
|
+
A missing or invalid configuration is a fatal init error: the process
|
|
66
|
+
writes a one-line diagnostic to **stderr** and exits with code `1`.
|
|
67
|
+
|
|
68
|
+
## Curl smoke test
|
|
69
|
+
|
|
70
|
+
With the server running locally (open mode):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Liveness
|
|
74
|
+
curl -s http://127.0.0.1:8787/health
|
|
75
|
+
|
|
76
|
+
# Tool catalog
|
|
77
|
+
curl -s http://127.0.0.1:8787/openapi.json | jq '.paths | keys'
|
|
78
|
+
|
|
79
|
+
# Web search
|
|
80
|
+
curl -s -X POST http://127.0.0.1:8787/tools/iflow_web_search \
|
|
81
|
+
-H 'Content-Type: application/json' \
|
|
82
|
+
-d '{"query":"site:nytimes.com OpenAI","count":3}' | jq .
|
|
83
|
+
|
|
84
|
+
# Image search
|
|
85
|
+
curl -s -X POST http://127.0.0.1:8787/tools/iflow_image_search \
|
|
86
|
+
-H 'Content-Type: application/json' \
|
|
87
|
+
-d '{"query":"snow leopard","count":3}' | jq .
|
|
88
|
+
|
|
89
|
+
# Web fetch
|
|
90
|
+
curl -s -X POST http://127.0.0.1:8787/tools/iflow_web_fetch \
|
|
91
|
+
-H 'Content-Type: application/json' \
|
|
92
|
+
-d '{"url":"https://zh.wikipedia.org/wiki/%E8%A5%BF%E6%B9%96"}' | jq .
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
With `IFLOW_OPENAPI_AUTH_TOKEN` set, add the bearer header to every call
|
|
96
|
+
except `/health`:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
curl -s -X POST http://127.0.0.1:8787/tools/iflow_web_search \
|
|
100
|
+
-H 'Authorization: Bearer YOUR_OPENAPI_AUTH_TOKEN' \
|
|
101
|
+
-H 'Content-Type: application/json' \
|
|
102
|
+
-d '{"query":"hello"}'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Response shapes
|
|
106
|
+
|
|
107
|
+
Success:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"ok": true,
|
|
112
|
+
"data": {
|
|
113
|
+
"query": "...",
|
|
114
|
+
"count": 3,
|
|
115
|
+
"tookMs": 412,
|
|
116
|
+
"results": [
|
|
117
|
+
{ "title": "...", "url": "...", "snippet": "...", "position": 1, "date": null }
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Error (uniform across all routes):
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"ok": false,
|
|
128
|
+
"error": {
|
|
129
|
+
"code": "invalid_param",
|
|
130
|
+
"message": "Parameter \"query\" is required.",
|
|
131
|
+
"status": 400
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Error codes come straight from `@iflow-ai/search-core`: `missing_api_key`,
|
|
137
|
+
`missing_param`, `invalid_param`, `network_timeout`, `network_error`,
|
|
138
|
+
`api_error`, `api_business_error`. The server adds three HTTP-level codes
|
|
139
|
+
for problems before a tool ever runs: `unauthorized`,
|
|
140
|
+
`method_not_allowed`, `not_found`, `invalid_input`, `payload_too_large`.
|
|
141
|
+
|
|
142
|
+
For `api_error`, iFlow's upstream HTTP status (401 / 403 / 429 / 5xx) is
|
|
143
|
+
preserved on the response so platforms can react to rate limits or auth
|
|
144
|
+
failures correctly.
|
|
145
|
+
|
|
146
|
+
## Open WebUI
|
|
147
|
+
|
|
148
|
+
Open WebUI consumes OpenAPI tool servers directly. After starting
|
|
149
|
+
`iflow-search-openapi` somewhere it can reach (Docker compose, k8s, a
|
|
150
|
+
sidecar, or `npx` on the same host):
|
|
151
|
+
|
|
152
|
+
1. **Settings → Tools → Add → OpenAPI URL.**
|
|
153
|
+
2. URL: `http://<host>:8787/openapi.json`.
|
|
154
|
+
3. If you launched the server with `IFLOW_OPENAPI_AUTH_TOKEN` set, paste
|
|
155
|
+
the same token into Open WebUI's *Bearer Token* field for the tool.
|
|
156
|
+
Leave blank for open mode.
|
|
157
|
+
4. Save. The three `iflow_*` tools appear in the chat tool picker.
|
|
158
|
+
|
|
159
|
+
A typical Docker Compose service:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
services:
|
|
163
|
+
iflow-search-openapi:
|
|
164
|
+
image: node:20-alpine
|
|
165
|
+
command: npx -y @iflow-ai/search-openapi@next
|
|
166
|
+
environment:
|
|
167
|
+
IFLOW_API_KEY: YOUR_IFLOW_API_KEY
|
|
168
|
+
IFLOW_OPENAPI_AUTH_TOKEN: YOUR_OPENAPI_AUTH_TOKEN
|
|
169
|
+
IFLOW_OPENAPI_CLIENT: open-webui
|
|
170
|
+
ports:
|
|
171
|
+
- "8787:8787"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Then in Open WebUI:
|
|
175
|
+
|
|
176
|
+
- URL: `http://iflow-search-openapi:8787/openapi.json` (or
|
|
177
|
+
`http://localhost:8787/openapi.json` if you're running Open WebUI on
|
|
178
|
+
the host)
|
|
179
|
+
- Bearer Token: the value of `IFLOW_OPENAPI_AUTH_TOKEN`
|
|
180
|
+
|
|
181
|
+
## Coze (custom tool / plugin)
|
|
182
|
+
|
|
183
|
+
Coze can register an external tool from an OpenAPI 3.x document:
|
|
184
|
+
|
|
185
|
+
1. **Plugins → Create plugin → Import from OpenAPI.**
|
|
186
|
+
2. URL: `https://<your-host>/openapi.json`.
|
|
187
|
+
3. Authentication: *Bearer Token*. Paste the value of
|
|
188
|
+
`IFLOW_OPENAPI_AUTH_TOKEN` if set; leave blank for open mode.
|
|
189
|
+
4. Select all three tools to expose to the agent.
|
|
190
|
+
|
|
191
|
+
For Coze you generally need a publicly reachable URL — terminate TLS in
|
|
192
|
+
front of the server (Caddy, Nginx, your platform's load balancer) and
|
|
193
|
+
keep `IFLOW_OPENAPI_AUTH_TOKEN` non-empty so the endpoint is not open
|
|
194
|
+
to the internet.
|
|
195
|
+
|
|
196
|
+
## Programmatic API
|
|
197
|
+
|
|
198
|
+
The package also exports the building blocks so you can embed the
|
|
199
|
+
listener in a larger HTTP app or drive it from integration tests:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { createIFlowSearchClient } from "@iflow-ai/search-core";
|
|
203
|
+
import { createApp } from "@iflow-ai/search-openapi";
|
|
204
|
+
import { createServer } from "node:http";
|
|
205
|
+
|
|
206
|
+
const client = createIFlowSearchClient({
|
|
207
|
+
apiKey: process.env.IFLOW_API_KEY!,
|
|
208
|
+
source: "openapi",
|
|
209
|
+
integrationName: "@iflow-ai/search-openapi",
|
|
210
|
+
integrationVersion: "0.1.0-pre.0",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const app = createApp({ client, authToken: process.env.IFLOW_OPENAPI_AUTH_TOKEN });
|
|
214
|
+
createServer(app).listen(8787);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## What this package does NOT do
|
|
218
|
+
|
|
219
|
+
The MVP is deliberately small. The following are explicit non-goals for
|
|
220
|
+
this release:
|
|
221
|
+
|
|
222
|
+
- No TLS termination. Put a reverse proxy in front for production.
|
|
223
|
+
- No per-platform packages (`@iflow-ai/search-open-webui`,
|
|
224
|
+
`@iflow-ai/search-coze`, …). One generic OpenAPI server covers them
|
|
225
|
+
all by design.
|
|
226
|
+
- No streaming responses, no SSE, no WebSocket.
|
|
227
|
+
- No multi-tenant hosting, no per-call API-key override.
|
|
228
|
+
- No bundled prompts or resources — only the three search tools.
|
|
229
|
+
|
|
230
|
+
## Attribution
|
|
231
|
+
|
|
232
|
+
Every outbound request to iFlow carries:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
IFlow-Source: openapi
|
|
236
|
+
IFlow-Integration: @iflow-ai/search-openapi
|
|
237
|
+
IFlow-Integration-Version: <pkg version>
|
|
238
|
+
User-Agent: @iflow-ai/search-openapi/<pkg version>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`IFLOW_OPENAPI_CLIENT` is **not** forwarded as `IFlow-MCP-Client` — that
|
|
242
|
+
header is reserved for MCP transports. It is kept in the startup banner
|
|
243
|
+
so operators can confirm which platform a given deployment is intended
|
|
244
|
+
to serve.
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT. See [`LICENSE`](./LICENSE).
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bearer-token auth for the HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Behavior:
|
|
5
|
+
* - If no `expectedToken` is configured, every request is allowed (open mode).
|
|
6
|
+
* - If `expectedToken` is set, the request must carry
|
|
7
|
+
* `Authorization: Bearer <expectedToken>`. Tokens are compared
|
|
8
|
+
* with timing-safe equality to remove the obvious side-channel.
|
|
9
|
+
*
|
|
10
|
+
* The /health route is checked separately by server.ts; this module
|
|
11
|
+
* does not know about route semantics.
|
|
12
|
+
*/
|
|
13
|
+
export type AuthCheckResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
status: 401;
|
|
18
|
+
code: "unauthorized";
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function checkBearer(authorizationHeader: string | undefined, expectedToken: string | undefined): AuthCheckResult;
|
|
22
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,wBAAgB,WAAW,CACzB,mBAAmB,EAAE,MAAM,GAAG,SAAS,EACvC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,eAAe,CA+CjB"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bearer-token auth for the HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Behavior:
|
|
5
|
+
* - If no `expectedToken` is configured, every request is allowed (open mode).
|
|
6
|
+
* - If `expectedToken` is set, the request must carry
|
|
7
|
+
* `Authorization: Bearer <expectedToken>`. Tokens are compared
|
|
8
|
+
* with timing-safe equality to remove the obvious side-channel.
|
|
9
|
+
*
|
|
10
|
+
* The /health route is checked separately by server.ts; this module
|
|
11
|
+
* does not know about route semantics.
|
|
12
|
+
*/
|
|
13
|
+
import { timingSafeEqual } from "node:crypto";
|
|
14
|
+
export function checkBearer(authorizationHeader, expectedToken) {
|
|
15
|
+
if (!expectedToken) {
|
|
16
|
+
return { ok: true };
|
|
17
|
+
}
|
|
18
|
+
const raw = (authorizationHeader ?? "").trim();
|
|
19
|
+
if (!raw) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
status: 401,
|
|
23
|
+
code: "unauthorized",
|
|
24
|
+
message: 'Missing Authorization header. Send "Authorization: Bearer <token>".',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const match = /^Bearer\s+(.+)$/i.exec(raw);
|
|
28
|
+
if (!match) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
status: 401,
|
|
32
|
+
code: "unauthorized",
|
|
33
|
+
message: "Authorization header must be of form `Bearer <token>`.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const provided = (match[1] ?? "").trim();
|
|
37
|
+
if (!provided) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
status: 401,
|
|
41
|
+
code: "unauthorized",
|
|
42
|
+
message: "Bearer token is empty.",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!safeEqual(provided, expectedToken)) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
status: 401,
|
|
49
|
+
code: "unauthorized",
|
|
50
|
+
message: "Invalid bearer token.",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { ok: true };
|
|
54
|
+
}
|
|
55
|
+
function safeEqual(a, b) {
|
|
56
|
+
const aBuf = Buffer.from(a, "utf8");
|
|
57
|
+
const bBuf = Buffer.from(b, "utf8");
|
|
58
|
+
if (aBuf.length !== bBuf.length) {
|
|
59
|
+
// Still do a constant-time compare on equal-length buffers to avoid
|
|
60
|
+
// leaking length differences via timing — compare two identical
|
|
61
|
+
// throwaway buffers so we always do the same amount of work.
|
|
62
|
+
const dummy = Buffer.alloc(aBuf.length);
|
|
63
|
+
timingSafeEqual(aBuf, dummy);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAM9C,MAAM,UAAU,WAAW,CACzB,mBAAuC,EACvC,aAAiC;IAEjC,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,MAAM,GAAG,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,cAAc;YACpB,OAAO,EACL,qEAAqE;SACxE,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,cAAc;YACpB,OAAO,EACL,wDAAwD;SAC3D,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,wBAAwB;SAClC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,CAAC;QACxC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,uBAAuB;SACjC,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS;IACrC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,oEAAoE;QACpE,gEAAgE;QAChE,6DAA6D;QAC7D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC"}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `iflow-search-openapi` — HTTP/OpenAPI tool server for the iFlow Search API.
|
|
4
|
+
*
|
|
5
|
+
* Spawned as a regular Node process; listens on PORT (default 8787) and
|
|
6
|
+
* answers HTTP. Intended for Open WebUI / Coze and other OpenAPI 3.x
|
|
7
|
+
* tool hosts.
|
|
8
|
+
*
|
|
9
|
+
* stderr is used for the startup banner and shutdown messages. The server
|
|
10
|
+
* itself does not log per-request lines — platforms typically want to do
|
|
11
|
+
* that in front via a reverse proxy.
|
|
12
|
+
*
|
|
13
|
+
* Exit codes:
|
|
14
|
+
* 0 — clean shutdown after SIGINT / SIGTERM
|
|
15
|
+
* 1 — configuration error or listener failure
|
|
16
|
+
*
|
|
17
|
+
* Attribution: source=`openapi`, integration=`@iflow-ai/search-openapi`.
|
|
18
|
+
* IFLOW_OPENAPI_CLIENT is captured into `clientName` for local visibility
|
|
19
|
+
* (printed in the banner) but is intentionally NOT forwarded to
|
|
20
|
+
* createIFlowSearchClient — the `IFlow-MCP-Client` header is reserved for
|
|
21
|
+
* MCP hosts and reusing it from a non-MCP transport would muddy analytics.
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=bin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG"}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `iflow-search-openapi` — HTTP/OpenAPI tool server for the iFlow Search API.
|
|
4
|
+
*
|
|
5
|
+
* Spawned as a regular Node process; listens on PORT (default 8787) and
|
|
6
|
+
* answers HTTP. Intended for Open WebUI / Coze and other OpenAPI 3.x
|
|
7
|
+
* tool hosts.
|
|
8
|
+
*
|
|
9
|
+
* stderr is used for the startup banner and shutdown messages. The server
|
|
10
|
+
* itself does not log per-request lines — platforms typically want to do
|
|
11
|
+
* that in front via a reverse proxy.
|
|
12
|
+
*
|
|
13
|
+
* Exit codes:
|
|
14
|
+
* 0 — clean shutdown after SIGINT / SIGTERM
|
|
15
|
+
* 1 — configuration error or listener failure
|
|
16
|
+
*
|
|
17
|
+
* Attribution: source=`openapi`, integration=`@iflow-ai/search-openapi`.
|
|
18
|
+
* IFLOW_OPENAPI_CLIENT is captured into `clientName` for local visibility
|
|
19
|
+
* (printed in the banner) but is intentionally NOT forwarded to
|
|
20
|
+
* createIFlowSearchClient — the `IFlow-MCP-Client` header is reserved for
|
|
21
|
+
* MCP hosts and reusing it from a non-MCP transport would muddy analytics.
|
|
22
|
+
*/
|
|
23
|
+
import { createServer } from "node:http";
|
|
24
|
+
import { createIFlowSearchClient } from "@iflow-ai/search-core";
|
|
25
|
+
import { ConfigError, loadConfig } from "./config.js";
|
|
26
|
+
import { createApp } from "./server.js";
|
|
27
|
+
import { INTEGRATION_NAME, SOURCE, VERSION } from "./version.js";
|
|
28
|
+
async function main() {
|
|
29
|
+
let config;
|
|
30
|
+
try {
|
|
31
|
+
config = loadConfig();
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (err instanceof ConfigError) {
|
|
35
|
+
process.stderr.write(`[${INTEGRATION_NAME}] ${err.message}\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
const client = createIFlowSearchClient({
|
|
41
|
+
apiKey: config.apiKey,
|
|
42
|
+
baseUrl: config.baseUrl,
|
|
43
|
+
timeoutMs: config.timeoutMs,
|
|
44
|
+
source: SOURCE,
|
|
45
|
+
integrationName: INTEGRATION_NAME,
|
|
46
|
+
integrationVersion: VERSION,
|
|
47
|
+
});
|
|
48
|
+
const app = createApp({
|
|
49
|
+
client,
|
|
50
|
+
authToken: config.authToken,
|
|
51
|
+
});
|
|
52
|
+
const httpServer = createServer(app);
|
|
53
|
+
const shutdown = (signal) => {
|
|
54
|
+
process.stderr.write(`[${INTEGRATION_NAME}] received ${signal}, closing.\n`);
|
|
55
|
+
httpServer.close((err) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
process.stderr.write(`[${INTEGRATION_NAME}] error during shutdown: ${err.message}\n`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
process.exit(0);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
64
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
65
|
+
httpServer.on("error", (err) => {
|
|
66
|
+
process.stderr.write(`[${INTEGRATION_NAME}] listen error: ${err.message}\n`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|
|
69
|
+
httpServer.listen(config.port, () => {
|
|
70
|
+
const address = httpServer.address();
|
|
71
|
+
const boundPort = typeof address === "object" && address !== null ? address.port : config.port;
|
|
72
|
+
const authNote = config.authToken
|
|
73
|
+
? "bearer auth ENABLED"
|
|
74
|
+
: "bearer auth DISABLED (open mode)";
|
|
75
|
+
const clientNote = config.clientName ? ` client=${config.clientName}` : "";
|
|
76
|
+
process.stderr.write(`[${INTEGRATION_NAME}] v${VERSION} listening on http://0.0.0.0:${boundPort} — ${authNote}${clientNote}\n`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
main().catch((err) => {
|
|
80
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
81
|
+
process.stderr.write(`[${INTEGRATION_NAME}] fatal: ${message}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env-only configuration for the iFlow Search OpenAPI server.
|
|
3
|
+
*
|
|
4
|
+
* IFLOW_API_KEY (required) — forwarded to search-core, sent as Authorization: Bearer ...
|
|
5
|
+
* IFLOW_BASE_URL (optional) — defaults to search-core's default
|
|
6
|
+
* IFLOW_TIMEOUT_MS (optional) — positive integer milliseconds
|
|
7
|
+
* PORT (optional) — listen port, defaults to 8787
|
|
8
|
+
* IFLOW_OPENAPI_AUTH_TOKEN (optional) — if set, all routes except /health require
|
|
9
|
+
* `Authorization: Bearer <token>`. Token is
|
|
10
|
+
* checked with constant-time comparison.
|
|
11
|
+
* IFLOW_OPENAPI_CLIENT (optional) — identifies the host platform (open-webui,
|
|
12
|
+
* coze, ...). Allowed: [a-z0-9._-]{1,64}.
|
|
13
|
+
* Not currently wired into a wire header — kept
|
|
14
|
+
* for future platform attribution. Stored on
|
|
15
|
+
* ResolvedConfig.clientName so server.ts and
|
|
16
|
+
* tests can observe it.
|
|
17
|
+
*
|
|
18
|
+
* Errors thrown here are init-time fatal: bin.ts prints them to stderr
|
|
19
|
+
* and exits non-zero BEFORE the HTTP listener is wired up.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_PORT = 8787;
|
|
22
|
+
export interface ResolvedConfig {
|
|
23
|
+
apiKey: string;
|
|
24
|
+
baseUrl: string | undefined;
|
|
25
|
+
timeoutMs: number | undefined;
|
|
26
|
+
port: number;
|
|
27
|
+
authToken: string | undefined;
|
|
28
|
+
clientName: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
export interface EnvLike {
|
|
31
|
+
IFLOW_API_KEY?: string | undefined;
|
|
32
|
+
IFLOW_BASE_URL?: string | undefined;
|
|
33
|
+
IFLOW_TIMEOUT_MS?: string | undefined;
|
|
34
|
+
PORT?: string | undefined;
|
|
35
|
+
IFLOW_OPENAPI_AUTH_TOKEN?: string | undefined;
|
|
36
|
+
IFLOW_OPENAPI_CLIENT?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
export declare class ConfigError extends Error {
|
|
39
|
+
constructor(message: string);
|
|
40
|
+
}
|
|
41
|
+
export declare function loadConfig(env?: EnvLike): ResolvedConfig;
|
|
42
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,OAAO;IACtB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAID,wBAAgB,UAAU,CAAC,GAAG,GAAE,OAAqB,GAAG,cAAc,CAwDrE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env-only configuration for the iFlow Search OpenAPI server.
|
|
3
|
+
*
|
|
4
|
+
* IFLOW_API_KEY (required) — forwarded to search-core, sent as Authorization: Bearer ...
|
|
5
|
+
* IFLOW_BASE_URL (optional) — defaults to search-core's default
|
|
6
|
+
* IFLOW_TIMEOUT_MS (optional) — positive integer milliseconds
|
|
7
|
+
* PORT (optional) — listen port, defaults to 8787
|
|
8
|
+
* IFLOW_OPENAPI_AUTH_TOKEN (optional) — if set, all routes except /health require
|
|
9
|
+
* `Authorization: Bearer <token>`. Token is
|
|
10
|
+
* checked with constant-time comparison.
|
|
11
|
+
* IFLOW_OPENAPI_CLIENT (optional) — identifies the host platform (open-webui,
|
|
12
|
+
* coze, ...). Allowed: [a-z0-9._-]{1,64}.
|
|
13
|
+
* Not currently wired into a wire header — kept
|
|
14
|
+
* for future platform attribution. Stored on
|
|
15
|
+
* ResolvedConfig.clientName so server.ts and
|
|
16
|
+
* tests can observe it.
|
|
17
|
+
*
|
|
18
|
+
* Errors thrown here are init-time fatal: bin.ts prints them to stderr
|
|
19
|
+
* and exits non-zero BEFORE the HTTP listener is wired up.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_PORT = 8787;
|
|
22
|
+
export class ConfigError extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "ConfigError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const CLIENT_NAME_PATTERN = /^[a-z0-9._-]{1,64}$/u;
|
|
29
|
+
export function loadConfig(env = process.env) {
|
|
30
|
+
const apiKey = (env.IFLOW_API_KEY ?? "").trim();
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new ConfigError("IFLOW_API_KEY is required. Set it in your environment, e.g. " +
|
|
33
|
+
"IFLOW_API_KEY=YOUR_IFLOW_API_KEY. Never commit the real key.");
|
|
34
|
+
}
|
|
35
|
+
const rawBase = env.IFLOW_BASE_URL?.trim();
|
|
36
|
+
const baseUrl = rawBase && rawBase.length > 0 ? rawBase : undefined;
|
|
37
|
+
const rawTimeout = env.IFLOW_TIMEOUT_MS?.trim();
|
|
38
|
+
let timeoutMs;
|
|
39
|
+
if (rawTimeout && rawTimeout.length > 0) {
|
|
40
|
+
const parsed = Number(rawTimeout);
|
|
41
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
|
|
42
|
+
throw new ConfigError(`IFLOW_TIMEOUT_MS must be a positive integer (milliseconds). Got: ${JSON.stringify(rawTimeout)}.`);
|
|
43
|
+
}
|
|
44
|
+
timeoutMs = parsed;
|
|
45
|
+
}
|
|
46
|
+
const rawPort = env.PORT?.trim();
|
|
47
|
+
let port = DEFAULT_PORT;
|
|
48
|
+
if (rawPort && rawPort.length > 0) {
|
|
49
|
+
const parsed = Number(rawPort);
|
|
50
|
+
if (!Number.isFinite(parsed) ||
|
|
51
|
+
!Number.isInteger(parsed) ||
|
|
52
|
+
parsed < 0 ||
|
|
53
|
+
parsed > 65535) {
|
|
54
|
+
throw new ConfigError(`PORT must be an integer in [0, 65535]. Got: ${JSON.stringify(rawPort)}.`);
|
|
55
|
+
}
|
|
56
|
+
port = parsed;
|
|
57
|
+
}
|
|
58
|
+
const rawToken = env.IFLOW_OPENAPI_AUTH_TOKEN?.trim();
|
|
59
|
+
const authToken = rawToken && rawToken.length > 0 ? rawToken : undefined;
|
|
60
|
+
const rawClient = env.IFLOW_OPENAPI_CLIENT?.trim();
|
|
61
|
+
let clientName;
|
|
62
|
+
if (rawClient && rawClient.length > 0) {
|
|
63
|
+
if (!CLIENT_NAME_PATTERN.test(rawClient)) {
|
|
64
|
+
throw new ConfigError(`IFLOW_OPENAPI_CLIENT must match [a-z0-9._-]{1,64}. Got: ${JSON.stringify(rawClient)}.`);
|
|
65
|
+
}
|
|
66
|
+
clientName = rawClient;
|
|
67
|
+
}
|
|
68
|
+
return { apiKey, baseUrl, timeoutMs, port, authToken, clientName };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP error JSON shape used by every endpoint. The OpenAPI spec
|
|
3
|
+
* advertises the same shape so Open WebUI / Coze and other OpenAPI
|
|
4
|
+
* tool hosts can render errors uniformly.
|
|
5
|
+
*
|
|
6
|
+
* iFlow errors come pre-shaped from @iflow-ai/search-core (stable codes
|
|
7
|
+
* + messages). This module never re-derives codes — it just maps to
|
|
8
|
+
* HTTP status codes and JSON output.
|
|
9
|
+
*
|
|
10
|
+
* For api_error specifically, iFlow's own HTTP status (when present on
|
|
11
|
+
* `IFlowError.status`) wins over the default — that preserves the
|
|
12
|
+
* upstream 401 / 403 / 429 signal to clients who key off status codes.
|
|
13
|
+
*/
|
|
14
|
+
import type { IFlowError } from "@iflow-ai/search-core";
|
|
15
|
+
export interface ErrorBody {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: {
|
|
18
|
+
code: string;
|
|
19
|
+
message: string;
|
|
20
|
+
status?: number;
|
|
21
|
+
detail?: unknown;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export declare function statusForIFlowError(error: IFlowError): number;
|
|
25
|
+
export declare function iflowErrorToBody(error: IFlowError): ErrorBody;
|
|
26
|
+
export declare function genericErrorBody(code: string, message: string): ErrorBody;
|
|
27
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,uBAAuB,CAAC;AAExE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;CACH;AAYD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAU7D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,CAW7D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAEzE"}
|