@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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +248 -0
  3. package/dist/auth.d.ts +22 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +68 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/bin.d.ts +24 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +84 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/config.d.ts +42 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +70 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/errors.d.ts +27 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +49 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/handlers/image-search.d.ts +8 -0
  20. package/dist/handlers/image-search.d.ts.map +1 -0
  21. package/dist/handlers/image-search.js +53 -0
  22. package/dist/handlers/image-search.js.map +1 -0
  23. package/dist/handlers/index.d.ts +13 -0
  24. package/dist/handlers/index.d.ts.map +1 -0
  25. package/dist/handlers/index.js +15 -0
  26. package/dist/handlers/index.js.map +1 -0
  27. package/dist/handlers/types.d.ts +37 -0
  28. package/dist/handlers/types.d.ts.map +1 -0
  29. package/dist/handlers/types.js +13 -0
  30. package/dist/handlers/types.js.map +1 -0
  31. package/dist/handlers/web-fetch.d.ts +8 -0
  32. package/dist/handlers/web-fetch.d.ts.map +1 -0
  33. package/dist/handlers/web-fetch.js +41 -0
  34. package/dist/handlers/web-fetch.js.map +1 -0
  35. package/dist/handlers/web-search.d.ts +10 -0
  36. package/dist/handlers/web-search.d.ts.map +1 -0
  37. package/dist/handlers/web-search.js +55 -0
  38. package/dist/handlers/web-search.js.map +1 -0
  39. package/dist/index.d.ts +13 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +13 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/openapi.d.ts +19 -0
  44. package/dist/openapi.d.ts.map +1 -0
  45. package/dist/openapi.js +112 -0
  46. package/dist/openapi.js.map +1 -0
  47. package/dist/server.d.ts +31 -0
  48. package/dist/server.d.ts.map +1 -0
  49. package/dist/server.js +150 -0
  50. package/dist/server.js.map +1 -0
  51. package/dist/version.d.ts +4 -0
  52. package/dist/version.d.ts.map +1 -0
  53. package/dist/version.js +7 -0
  54. package/dist/version.js.map +1 -0
  55. 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
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -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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"}
@@ -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"}