@zihin/agent-client 0.1.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/README.md +156 -0
- package/dist/agents.d.ts +21 -0
- package/dist/agents.js +52 -0
- package/dist/agents.js.map +1 -0
- package/dist/client.d.ts +43 -0
- package/dist/client.js +75 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.js +89 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +98 -0
- package/dist/http.js +281 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +13 -0
- package/dist/internal.js +40 -0
- package/dist/internal.js.map +1 -0
- package/dist/invoke.d.ts +18 -0
- package/dist/invoke.js +233 -0
- package/dist/invoke.js.map +1 -0
- package/dist/security.d.ts +82 -0
- package/dist/security.js +251 -0
- package/dist/security.js.map +1 -0
- package/dist/sse.d.ts +27 -0
- package/dist/sse.js +138 -0
- package/dist/sse.js.map +1 -0
- package/dist/triggers.d.ts +43 -0
- package/dist/triggers.js +241 -0
- package/dist/triggers.js.map +1 -0
- package/dist/types.d.ts +268 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
package/dist/invoke.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Caminho A — invocação direta de agente (POST /api/v2/agents/:id/stream).
|
|
4
|
+
* `streamAgent` emite deltas ao vivo; `invokeAgent` buffeia o stream e consolida
|
|
5
|
+
* o evento `response` (content/sources/session_id/blocks) com o `metrics` (usage/model).
|
|
6
|
+
*
|
|
7
|
+
* @module invoke
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.streamAgent = streamAgent;
|
|
11
|
+
exports.invokeAgent = invokeAgent;
|
|
12
|
+
const errors_1 = require("./errors");
|
|
13
|
+
const http_1 = require("./http");
|
|
14
|
+
const sse_1 = require("./sse");
|
|
15
|
+
const internal_1 = require("./internal");
|
|
16
|
+
const security_1 = require("./security");
|
|
17
|
+
/** Mapeia um evento SSE bruto para o chunk normalizado do núcleo. */
|
|
18
|
+
function mapEvent(e) {
|
|
19
|
+
const d = (0, internal_1.rec)(e.data);
|
|
20
|
+
switch (e.event) {
|
|
21
|
+
case 'metadata':
|
|
22
|
+
return {
|
|
23
|
+
type: 'metadata',
|
|
24
|
+
requestId: (0, internal_1.str)(d.request_id),
|
|
25
|
+
agentId: (0, internal_1.str)(d.agent_id),
|
|
26
|
+
sessionId: (0, internal_1.str)(d.session_id),
|
|
27
|
+
data: e.data,
|
|
28
|
+
};
|
|
29
|
+
case 'status':
|
|
30
|
+
return { type: 'status', phase: (0, internal_1.str)(d.phase), data: e.data };
|
|
31
|
+
case 'attachments':
|
|
32
|
+
return { type: 'attachments', data: e.data };
|
|
33
|
+
case 'phase':
|
|
34
|
+
return { type: 'phase', phase: (0, internal_1.str)(d.phase), data: e.data };
|
|
35
|
+
case 'thinking':
|
|
36
|
+
return { type: 'thinking', thought: (0, internal_1.str)(d.thought), data: e.data };
|
|
37
|
+
case 'tool':
|
|
38
|
+
return {
|
|
39
|
+
type: 'tool',
|
|
40
|
+
tool: (0, internal_1.str)(d.tool),
|
|
41
|
+
callId: (0, internal_1.str)(d.call_id),
|
|
42
|
+
success: typeof d.success === 'boolean' ? d.success : undefined,
|
|
43
|
+
data: e.data,
|
|
44
|
+
};
|
|
45
|
+
case 'action_proposed':
|
|
46
|
+
return { type: 'action_proposed', data: e.data };
|
|
47
|
+
case 'token':
|
|
48
|
+
return { type: 'token', content: (0, internal_1.str)(d.content) ?? '', data: e.data };
|
|
49
|
+
case 'warning':
|
|
50
|
+
return { type: 'warning', message: (0, internal_1.str)(d.message), data: e.data };
|
|
51
|
+
case 'response':
|
|
52
|
+
return {
|
|
53
|
+
type: 'response',
|
|
54
|
+
content: (0, internal_1.str)(d.content) ?? '',
|
|
55
|
+
sessionId: (0, internal_1.str)(d.session_id),
|
|
56
|
+
sources: Array.isArray(d.sources) ? d.sources : undefined,
|
|
57
|
+
blocks: Array.isArray(d.blocks)
|
|
58
|
+
? d.blocks
|
|
59
|
+
: undefined,
|
|
60
|
+
data: e.data,
|
|
61
|
+
};
|
|
62
|
+
case 'metrics': {
|
|
63
|
+
const modelConfig = (0, internal_1.rec)(d.model_config);
|
|
64
|
+
return {
|
|
65
|
+
type: 'metrics',
|
|
66
|
+
usage: (0, internal_1.parseUsage)(d.usage),
|
|
67
|
+
model: (0, internal_1.str)(modelConfig.model) ?? (0, internal_1.str)(d.model),
|
|
68
|
+
iterations: (0, internal_1.num)(d.iterations),
|
|
69
|
+
executionTimeMs: (0, internal_1.num)(d.execution_time_ms),
|
|
70
|
+
data: e.data,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
return { type: 'unknown', event: e.event, data: e.data };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function buildRequestBody(params) {
|
|
78
|
+
const body = {};
|
|
79
|
+
if (params.message !== undefined)
|
|
80
|
+
body.message = params.message;
|
|
81
|
+
if (params.sessionId)
|
|
82
|
+
body.session_id = params.sessionId;
|
|
83
|
+
if (params.attachments?.length) {
|
|
84
|
+
body.attachments = params.attachments.map((a) => ({ url: a.url, type: a.type }));
|
|
85
|
+
}
|
|
86
|
+
if (params.options) {
|
|
87
|
+
const o = {};
|
|
88
|
+
if (params.options.temperature !== undefined)
|
|
89
|
+
o.temperature = params.options.temperature;
|
|
90
|
+
if (params.options.maxTokens !== undefined)
|
|
91
|
+
o.max_tokens = params.options.maxTokens;
|
|
92
|
+
if (params.options.timeoutMs !== undefined)
|
|
93
|
+
o.timeout_ms = params.options.timeoutMs;
|
|
94
|
+
if (params.options.jitMode !== undefined)
|
|
95
|
+
o.jit_mode = params.options.jitMode;
|
|
96
|
+
if (Object.keys(o).length)
|
|
97
|
+
body.options = o;
|
|
98
|
+
}
|
|
99
|
+
if (params.metadata) {
|
|
100
|
+
const m = { ...params.metadata };
|
|
101
|
+
if (params.metadata.traceId !== undefined) {
|
|
102
|
+
m.trace_id = params.metadata.traceId;
|
|
103
|
+
delete m.traceId;
|
|
104
|
+
}
|
|
105
|
+
if (params.metadata.correlationId !== undefined) {
|
|
106
|
+
m.correlation_id = params.metadata.correlationId;
|
|
107
|
+
delete m.correlationId;
|
|
108
|
+
}
|
|
109
|
+
body.metadata = m;
|
|
110
|
+
}
|
|
111
|
+
return body;
|
|
112
|
+
}
|
|
113
|
+
function validateParams(params) {
|
|
114
|
+
if (!params.agentId) {
|
|
115
|
+
throw new errors_1.ZihinAgentError('config', 'agentId is required.');
|
|
116
|
+
}
|
|
117
|
+
if (!params.message && !(params.attachments && params.attachments.length)) {
|
|
118
|
+
throw new errors_1.ZihinAgentError('config', 'Either `message` or `attachments` is required.');
|
|
119
|
+
}
|
|
120
|
+
if (params.message) {
|
|
121
|
+
const sizeCheck = (0, security_1.validatePromptSize)(params.message);
|
|
122
|
+
if (!sizeCheck.valid) {
|
|
123
|
+
throw new errors_1.ZihinAgentError('config', sizeCheck.error || 'Message too large.');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Defense-in-depth: anexos vêm do usuário e o BE os busca (server-side fetch).
|
|
127
|
+
// Valida HTTPS + bloqueia IP privado (mesma regra da callbackUrl); domínio livre.
|
|
128
|
+
for (const att of params.attachments ?? []) {
|
|
129
|
+
const urlCheck = (0, security_1.validateBaseUrl)(att.url, { allowCustomDomains: true });
|
|
130
|
+
if (!urlCheck.valid) {
|
|
131
|
+
throw new errors_1.ZihinAgentError('config', `Invalid attachment URL: ${urlCheck.error}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Emite chunks normalizados do agente ao vivo. Para quem quer streaming (UI, logs).
|
|
137
|
+
* O deadline (timeout + abort do chamador) cobre toda a duração do stream e é liberado no fim.
|
|
138
|
+
*/
|
|
139
|
+
async function* streamAgent(cfg, params) {
|
|
140
|
+
validateParams(params);
|
|
141
|
+
const timeoutMs = params.options?.timeoutMs ?? cfg.timeoutMs;
|
|
142
|
+
const url = `${cfg.baseUrl}/api/v2/agents/${encodeURIComponent(params.agentId)}/stream`;
|
|
143
|
+
const { response, deadline, endpoint } = await (0, http_1.openStream)({
|
|
144
|
+
url,
|
|
145
|
+
headers: (0, http_1.buildHeaders)(cfg.apiKey),
|
|
146
|
+
body: JSON.stringify(buildRequestBody(params)),
|
|
147
|
+
timeoutMs,
|
|
148
|
+
maxRetries: cfg.maxRetries,
|
|
149
|
+
callerSignal: params.signal,
|
|
150
|
+
fetchImpl: cfg.fetchImpl,
|
|
151
|
+
onRetry: cfg.onRetry,
|
|
152
|
+
});
|
|
153
|
+
// dispose garantido aqui também (parseSse pode lançar "no body" antes do seu try/finally,
|
|
154
|
+
// o que deixaria o timer do deadline pendurado segurando o event loop / função serverless).
|
|
155
|
+
try {
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
let text = '';
|
|
158
|
+
try {
|
|
159
|
+
text = await response.text();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
/* ignore */
|
|
163
|
+
}
|
|
164
|
+
throw (0, errors_1.errorFromResponse)(response.status, (0, http_1.parseBody)(text), { endpoint });
|
|
165
|
+
}
|
|
166
|
+
const onAbortError = () => deadline.timedOut()
|
|
167
|
+
? new errors_1.ZihinAgentError('timeout', `Stream timed out after ${timeoutMs}ms.`, { endpoint })
|
|
168
|
+
: deadline.abortedByCaller()
|
|
169
|
+
? new errors_1.ZihinAgentError('aborted', 'Stream aborted by the caller.', { endpoint })
|
|
170
|
+
: new errors_1.ZihinAgentError('network', 'Stream connection interrupted.', { endpoint });
|
|
171
|
+
for await (const e of (0, sse_1.parseSse)(response, {
|
|
172
|
+
signal: params.signal,
|
|
173
|
+
onAbortError,
|
|
174
|
+
maxEventBytes: cfg.maxStreamEventBytes,
|
|
175
|
+
maxTotalBytes: cfg.maxStreamTotalBytes,
|
|
176
|
+
})) {
|
|
177
|
+
yield mapEvent(e);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
deadline.dispose();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Invoca o agente e retorna o resultado final consolidado. Buffeia o SSE até `response`+`metrics`.
|
|
186
|
+
*/
|
|
187
|
+
async function invokeAgent(cfg, params) {
|
|
188
|
+
const raw = [];
|
|
189
|
+
const keepRaw = params.includeRaw === true;
|
|
190
|
+
let responseChunk;
|
|
191
|
+
let metricsChunk;
|
|
192
|
+
let metadataSessionId;
|
|
193
|
+
let tokenBuffer = '';
|
|
194
|
+
for await (const chunk of streamAgent(cfg, params)) {
|
|
195
|
+
if (keepRaw)
|
|
196
|
+
raw.push(chunk);
|
|
197
|
+
if (chunk.type === 'response')
|
|
198
|
+
responseChunk = chunk;
|
|
199
|
+
else if (chunk.type === 'metrics')
|
|
200
|
+
metricsChunk = chunk;
|
|
201
|
+
else if (chunk.type === 'token')
|
|
202
|
+
tokenBuffer += chunk.content;
|
|
203
|
+
else if (chunk.type === 'metadata' && chunk.sessionId)
|
|
204
|
+
metadataSessionId = chunk.sessionId;
|
|
205
|
+
}
|
|
206
|
+
if (!responseChunk) {
|
|
207
|
+
// Stream terminou sem o evento final — fallback para tokens, ou erro de protocolo.
|
|
208
|
+
if (tokenBuffer) {
|
|
209
|
+
return {
|
|
210
|
+
content: tokenBuffer,
|
|
211
|
+
sessionId: metadataSessionId ?? params.sessionId,
|
|
212
|
+
usage: metricsChunk?.usage,
|
|
213
|
+
model: metricsChunk?.model,
|
|
214
|
+
iterations: metricsChunk?.iterations,
|
|
215
|
+
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
216
|
+
raw,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
throw new errors_1.ZihinAgentError('protocol', 'Stream ended without a `response` event.');
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
content: responseChunk.content || tokenBuffer,
|
|
223
|
+
sessionId: responseChunk.sessionId ?? metadataSessionId ?? params.sessionId,
|
|
224
|
+
model: metricsChunk?.model,
|
|
225
|
+
usage: metricsChunk?.usage,
|
|
226
|
+
sources: responseChunk.sources,
|
|
227
|
+
blocks: responseChunk.blocks,
|
|
228
|
+
iterations: metricsChunk?.iterations,
|
|
229
|
+
executionTimeMs: metricsChunk?.executionTimeMs,
|
|
230
|
+
raw,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=invoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoke.js","sourceRoot":"","sources":["../src/invoke.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities — portado de n8n-nodes-zihin/src/utils/security.ts (zero-dep).
|
|
3
|
+
* SSRF prevention, payload limits, error sanitization, API-key format, HMAC signing.
|
|
4
|
+
*
|
|
5
|
+
* Núcleo compartilhado: os shells (n8n, Paperclip) reusam estas primitivas via @zihin/agent-client.
|
|
6
|
+
*
|
|
7
|
+
* @module security
|
|
8
|
+
*/
|
|
9
|
+
export interface UrlValidationResult {
|
|
10
|
+
valid: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
sanitizedUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface UrlValidationOptions {
|
|
15
|
+
allowCustomDomains?: boolean;
|
|
16
|
+
allowHttp?: boolean;
|
|
17
|
+
allowLocalhost?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validates a base URL for SSRF prevention.
|
|
21
|
+
*
|
|
22
|
+
* NOTA (S4): a checagem é por NOME de host, não pelo IP resolvido. Com `allowCustomDomains: true`,
|
|
23
|
+
* um host que resolve para IP privado (DNS rebinding) passa. Em ambiente sensível, mantenha o
|
|
24
|
+
* whitelist (default) ou faça a resolução/checagem de IP na borda de rede.
|
|
25
|
+
*
|
|
26
|
+
* @param url - The URL to validate
|
|
27
|
+
* @param options - Validation options
|
|
28
|
+
* @returns Validation result with sanitized URL or error
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateBaseUrl(url: string, options?: UrlValidationOptions): UrlValidationResult;
|
|
31
|
+
export interface PayloadLimits {
|
|
32
|
+
maxPromptBytes: number;
|
|
33
|
+
maxMessageCount: number;
|
|
34
|
+
maxSingleMessageBytes: number;
|
|
35
|
+
maxTotalPayloadBytes: number;
|
|
36
|
+
}
|
|
37
|
+
declare const DEFAULT_LIMITS: PayloadLimits;
|
|
38
|
+
export interface PayloadValidationResult {
|
|
39
|
+
valid: boolean;
|
|
40
|
+
error?: string;
|
|
41
|
+
truncated?: boolean;
|
|
42
|
+
details?: {
|
|
43
|
+
promptBytes?: number;
|
|
44
|
+
messageCount?: number;
|
|
45
|
+
largestMessageBytes?: number;
|
|
46
|
+
totalBytes?: number;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validates a single message/prompt size against configured limits.
|
|
51
|
+
*/
|
|
52
|
+
export declare function validatePromptSize(prompt: string, limits?: Partial<PayloadLimits>): PayloadValidationResult;
|
|
53
|
+
export interface SanitizedError {
|
|
54
|
+
message: string;
|
|
55
|
+
code?: string;
|
|
56
|
+
retryable: boolean;
|
|
57
|
+
retryAfter?: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sanitizes API errors to prevent information leakage (keys, request IDs, stack traces).
|
|
61
|
+
*/
|
|
62
|
+
export declare function sanitizeApiError(error: unknown): SanitizedError;
|
|
63
|
+
export interface ApiKeyValidationResult {
|
|
64
|
+
valid: boolean;
|
|
65
|
+
error?: string;
|
|
66
|
+
environment?: 'live' | 'test' | 'dev';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validates API key format (zhn_live_* | zhn_test_* | zhn_dev_*).
|
|
70
|
+
*/
|
|
71
|
+
export declare function validateApiKeyFormat(apiKey: string): ApiKeyValidationResult;
|
|
72
|
+
export interface SignedRequestHeaders {
|
|
73
|
+
'X-Zihin-Signature': string;
|
|
74
|
+
'X-Zihin-Timestamp': string;
|
|
75
|
+
'X-Zihin-Nonce': string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Signs a request payload with HMAC-SHA256 (timestamp + nonce + payload).
|
|
79
|
+
* Requer Node crypto — carregado sob demanda; lança em runtimes sem ele.
|
|
80
|
+
*/
|
|
81
|
+
export declare function signRequest(payload: string, secretKey: string, timestamp?: number): SignedRequestHeaders;
|
|
82
|
+
export { DEFAULT_LIMITS };
|
package/dist/security.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security utilities — portado de n8n-nodes-zihin/src/utils/security.ts (zero-dep).
|
|
4
|
+
* SSRF prevention, payload limits, error sanitization, API-key format, HMAC signing.
|
|
5
|
+
*
|
|
6
|
+
* Núcleo compartilhado: os shells (n8n, Paperclip) reusam estas primitivas via @zihin/agent-client.
|
|
7
|
+
*
|
|
8
|
+
* @module security
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.DEFAULT_LIMITS = void 0;
|
|
12
|
+
exports.validateBaseUrl = validateBaseUrl;
|
|
13
|
+
exports.validatePromptSize = validatePromptSize;
|
|
14
|
+
exports.sanitizeApiError = sanitizeApiError;
|
|
15
|
+
exports.validateApiKeyFormat = validateApiKeyFormat;
|
|
16
|
+
exports.signRequest = signRequest;
|
|
17
|
+
const internal_1 = require("./internal");
|
|
18
|
+
// `node:crypto` é carregado SOB DEMANDA (só em signRequest) para não quebrar o load do módulo
|
|
19
|
+
// em runtimes sem Node crypto (edge/browser), onde o resto do núcleo funciona via Web APIs.
|
|
20
|
+
// ============================================
|
|
21
|
+
// 1. URL Validation (SSRF Prevention)
|
|
22
|
+
// ============================================
|
|
23
|
+
const PRIVATE_IP_PATTERNS = [
|
|
24
|
+
/^127\./, // 127.0.0.0/8 (localhost)
|
|
25
|
+
/^10\./, // 10.0.0.0/8 (private)
|
|
26
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12 (private)
|
|
27
|
+
/^192\.168\./, // 192.168.0.0/16 (private)
|
|
28
|
+
/^169\.254\./, // 169.254.0.0/16 (link-local)
|
|
29
|
+
/^0\./, // 0.0.0.0/8
|
|
30
|
+
/^::1$/, // IPv6 localhost
|
|
31
|
+
/^fe80:/i, // IPv6 link-local
|
|
32
|
+
/^fc00:/i, // IPv6 unique local
|
|
33
|
+
/^fd00:/i, // IPv6 unique local
|
|
34
|
+
];
|
|
35
|
+
const BLOCKED_HOSTNAMES = [
|
|
36
|
+
'localhost',
|
|
37
|
+
'localtest.me',
|
|
38
|
+
'metadata.google.internal', // GCP metadata
|
|
39
|
+
'169.254.169.254', // AWS/Azure/GCP metadata
|
|
40
|
+
'metadata.azure.com',
|
|
41
|
+
];
|
|
42
|
+
const ALLOWED_ZIHIN_DOMAINS = [
|
|
43
|
+
'llm.zihin.ai',
|
|
44
|
+
'api.zihin.ai',
|
|
45
|
+
'staging.llm.zihin.ai',
|
|
46
|
+
'dev.llm.zihin.ai',
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Validates a base URL for SSRF prevention.
|
|
50
|
+
*
|
|
51
|
+
* NOTA (S4): a checagem é por NOME de host, não pelo IP resolvido. Com `allowCustomDomains: true`,
|
|
52
|
+
* um host que resolve para IP privado (DNS rebinding) passa. Em ambiente sensível, mantenha o
|
|
53
|
+
* whitelist (default) ou faça a resolução/checagem de IP na borda de rede.
|
|
54
|
+
*
|
|
55
|
+
* @param url - The URL to validate
|
|
56
|
+
* @param options - Validation options
|
|
57
|
+
* @returns Validation result with sanitized URL or error
|
|
58
|
+
*/
|
|
59
|
+
function validateBaseUrl(url, options = {}) {
|
|
60
|
+
// Empty/default URL is allowed
|
|
61
|
+
if (!url || url.trim() === '') {
|
|
62
|
+
return { valid: true, sanitizedUrl: 'https://llm.zihin.ai' };
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const parsed = new URL(url);
|
|
66
|
+
const hostname = parsed.hostname;
|
|
67
|
+
// Check if this is localhost (for development)
|
|
68
|
+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
|
69
|
+
// Allow localhost when explicitly enabled (development mode)
|
|
70
|
+
if (isLocalhost && options.allowLocalhost) {
|
|
71
|
+
const sanitizedUrl = parsed.origin + parsed.pathname.replace(/\/+$/, '');
|
|
72
|
+
return { valid: true, sanitizedUrl };
|
|
73
|
+
}
|
|
74
|
+
// 1. Protocol validation (HTTPS only by default)
|
|
75
|
+
if (!options.allowHttp && parsed.protocol !== 'https:') {
|
|
76
|
+
return {
|
|
77
|
+
valid: false,
|
|
78
|
+
error: 'Only HTTPS URLs are allowed for security reasons',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// 2. Block private IP ranges
|
|
82
|
+
for (const pattern of PRIVATE_IP_PATTERNS) {
|
|
83
|
+
if (pattern.test(hostname)) {
|
|
84
|
+
return { valid: false, error: 'Private IP addresses are not allowed' };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 3. Block known dangerous hostnames
|
|
88
|
+
for (const blocked of BLOCKED_HOSTNAMES) {
|
|
89
|
+
if (blocked.startsWith('*')) {
|
|
90
|
+
const suffix = blocked.slice(1);
|
|
91
|
+
if (hostname.endsWith(suffix)) {
|
|
92
|
+
return { valid: false, error: 'This hostname is not allowed' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (hostname === blocked || hostname.endsWith('.' + blocked)) {
|
|
96
|
+
return { valid: false, error: 'This hostname is not allowed' };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 4. Whitelist mode (only Zihin domains) unless custom domains allowed
|
|
100
|
+
if (!options.allowCustomDomains) {
|
|
101
|
+
const isAllowed = ALLOWED_ZIHIN_DOMAINS.some((domain) => hostname === domain || hostname.endsWith('.' + domain));
|
|
102
|
+
if (!isAllowed) {
|
|
103
|
+
return {
|
|
104
|
+
valid: false,
|
|
105
|
+
error: `Only Zihin domains are allowed. Use 'allowCustomBaseUrl' for custom endpoints.`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Remove trailing slash for consistency
|
|
110
|
+
const sanitizedUrl = parsed.origin + parsed.pathname.replace(/\/+$/, '');
|
|
111
|
+
return { valid: true, sanitizedUrl };
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return { valid: false, error: 'Invalid URL format' };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const DEFAULT_LIMITS = {
|
|
118
|
+
maxPromptBytes: 100 * 1024, // 100KB
|
|
119
|
+
maxMessageCount: 100,
|
|
120
|
+
maxSingleMessageBytes: 50 * 1024, // 50KB
|
|
121
|
+
maxTotalPayloadBytes: 500 * 1024, // 500KB
|
|
122
|
+
};
|
|
123
|
+
exports.DEFAULT_LIMITS = DEFAULT_LIMITS;
|
|
124
|
+
/**
|
|
125
|
+
* Validates a single message/prompt size against configured limits.
|
|
126
|
+
*/
|
|
127
|
+
function validatePromptSize(prompt, limits = {}) {
|
|
128
|
+
const effectiveLimits = { ...DEFAULT_LIMITS, ...limits };
|
|
129
|
+
const bytes = (0, internal_1.utf8Len)(prompt);
|
|
130
|
+
if (bytes > effectiveLimits.maxPromptBytes) {
|
|
131
|
+
return {
|
|
132
|
+
valid: false,
|
|
133
|
+
error: `Prompt size (${Math.round(bytes / 1024)}KB) exceeds maximum allowed (${Math.round(effectiveLimits.maxPromptBytes / 1024)}KB)`,
|
|
134
|
+
details: { promptBytes: bytes },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return { valid: true, details: { promptBytes: bytes } };
|
|
138
|
+
}
|
|
139
|
+
// ============================================
|
|
140
|
+
// 3. Error Sanitization
|
|
141
|
+
// ============================================
|
|
142
|
+
// Redação SÓ de segredos reais. request_id/trace_id/correlation_id NÃO são redatados:
|
|
143
|
+
// não são secretos e são exatamente o que o suporte/observabilidade precisa para correlacionar.
|
|
144
|
+
const SENSITIVE_PATTERNS = [
|
|
145
|
+
/zhn_(?:live|test|dev)_[a-zA-Z0-9]+/g, // API keys
|
|
146
|
+
/Bearer\s+[a-zA-Z0-9._-]+/gi, // Bearer tokens
|
|
147
|
+
/at\s+[\w./<>]+\s*\(.*:\d+:\d+\)/g, // Stack trace lines
|
|
148
|
+
/^\s+at\s+.+$/gm, // Stack traces
|
|
149
|
+
/Error:\s*.*\n\s+at/g, // Error stack beginnings
|
|
150
|
+
];
|
|
151
|
+
const ERROR_CODE_MAP = {
|
|
152
|
+
RATE_LIMIT_EXCEEDED: 'Rate limit exceeded. Please wait before retrying.',
|
|
153
|
+
UNAUTHORIZED: 'Authentication failed. Please check your API credentials.',
|
|
154
|
+
FORBIDDEN: 'This API key lacks permission for this action. Use an admin/editor key (member keys cannot create triggers or read telemetry).',
|
|
155
|
+
INVALID_MODEL: 'The specified model is not available.',
|
|
156
|
+
INTERNAL_ERROR: 'An internal error occurred. Please try again later.',
|
|
157
|
+
TIMEOUT: 'Request timed out. Try increasing the timeout.',
|
|
158
|
+
INVALID_REQUEST: 'The request was malformed. Please check your input.',
|
|
159
|
+
INSUFFICIENT_CREDITS: 'Insufficient credits. Please check your account balance.',
|
|
160
|
+
CONTEXT_LENGTH_EXCEEDED: 'The input exceeds the model context length limit.',
|
|
161
|
+
AGENT_NOT_FOUND: 'Agent not found or not active for this tenant.',
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Sanitizes API errors to prevent information leakage (keys, request IDs, stack traces).
|
|
165
|
+
*/
|
|
166
|
+
function sanitizeApiError(error) {
|
|
167
|
+
let message = 'An unexpected error occurred';
|
|
168
|
+
let code;
|
|
169
|
+
let retryable = false;
|
|
170
|
+
let retryAfter;
|
|
171
|
+
if (typeof error === 'object' && error !== null) {
|
|
172
|
+
const err = error;
|
|
173
|
+
// O BE usa o shape aninhado { error: { code, message } }; suporta também o plano
|
|
174
|
+
// { error|code: string, message }. (Antes, err.error objeto virava "código" e a msg sumia.)
|
|
175
|
+
const nested = err.error && typeof err.error === 'object' ? err.error : undefined;
|
|
176
|
+
const codeRaw = nested?.code ?? (typeof err.error === 'string' ? err.error : err.code);
|
|
177
|
+
code = typeof codeRaw === 'string' ? codeRaw : undefined;
|
|
178
|
+
const rawMessage = (nested?.message ?? err.message);
|
|
179
|
+
if (code && ERROR_CODE_MAP[code]) {
|
|
180
|
+
message = ERROR_CODE_MAP[code];
|
|
181
|
+
}
|
|
182
|
+
else if (typeof rawMessage === 'string' && rawMessage) {
|
|
183
|
+
message = rawMessage;
|
|
184
|
+
}
|
|
185
|
+
if (code === 'RATE_LIMIT_EXCEEDED' || code === 'TIMEOUT') {
|
|
186
|
+
retryable = true;
|
|
187
|
+
retryAfter = err.retryAfter || 60;
|
|
188
|
+
}
|
|
189
|
+
// S5: limita o tamanho ANTES das regex (bounda custo/ReDoS em mensagens grandes).
|
|
190
|
+
if (message.length > 2000)
|
|
191
|
+
message = message.slice(0, 2000);
|
|
192
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
193
|
+
message = message.replace(pattern, '[REDACTED]');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (typeof error === 'string') {
|
|
197
|
+
message = error;
|
|
198
|
+
if (message.length > 2000)
|
|
199
|
+
message = message.slice(0, 2000);
|
|
200
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
201
|
+
message = message.replace(pattern, '[REDACTED]');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (message.length > 500) {
|
|
205
|
+
message = message.substring(0, 500) + '...';
|
|
206
|
+
}
|
|
207
|
+
return { message, code, retryable, retryAfter };
|
|
208
|
+
}
|
|
209
|
+
// ============================================
|
|
210
|
+
// 4. API Key Validation
|
|
211
|
+
// ============================================
|
|
212
|
+
const API_KEY_PATTERN = /^zhn_(live|test|dev)_[a-zA-Z0-9]{20,}$/;
|
|
213
|
+
/**
|
|
214
|
+
* Validates API key format (zhn_live_* | zhn_test_* | zhn_dev_*).
|
|
215
|
+
*/
|
|
216
|
+
function validateApiKeyFormat(apiKey) {
|
|
217
|
+
if (!apiKey || apiKey.trim() === '') {
|
|
218
|
+
return { valid: false, error: 'API key is required' };
|
|
219
|
+
}
|
|
220
|
+
const trimmed = apiKey.trim();
|
|
221
|
+
if (!API_KEY_PATTERN.test(trimmed)) {
|
|
222
|
+
return {
|
|
223
|
+
valid: false,
|
|
224
|
+
error: 'Invalid API key format. Expected: zhn_live_xxx, zhn_test_xxx or zhn_dev_xxx',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const environment = trimmed.startsWith('zhn_live_')
|
|
228
|
+
? 'live'
|
|
229
|
+
: trimmed.startsWith('zhn_test_')
|
|
230
|
+
? 'test'
|
|
231
|
+
: 'dev';
|
|
232
|
+
return { valid: true, environment };
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Signs a request payload with HMAC-SHA256 (timestamp + nonce + payload).
|
|
236
|
+
* Requer Node crypto — carregado sob demanda; lança em runtimes sem ele.
|
|
237
|
+
*/
|
|
238
|
+
function signRequest(payload, secretKey, timestamp) {
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
240
|
+
const { createHmac, randomBytes } = require('node:crypto');
|
|
241
|
+
const ts = timestamp || Date.now();
|
|
242
|
+
const nonce = randomBytes(16).toString('hex');
|
|
243
|
+
const message = `${ts}.${nonce}.${payload}`;
|
|
244
|
+
const signature = createHmac('sha256', secretKey).update(message).digest('hex');
|
|
245
|
+
return {
|
|
246
|
+
'X-Zihin-Signature': `sha256=${signature}`,
|
|
247
|
+
'X-Zihin-Timestamp': ts.toString(),
|
|
248
|
+
'X-Zihin-Nonce': nonce,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AA8DH,0CAsEC;AAmCD,gDAkBC;AAwCD,4CA8CC;AAiBD,oDAqBC;AAgBD,kCAkBC;AArVD,yCAAqC;AAErC,8FAA8F;AAC9F,4FAA4F;AAE5F,+CAA+C;AAC/C,sCAAsC;AACtC,+CAA+C;AAE/C,MAAM,mBAAmB,GAAG;IAC3B,QAAQ,EAAE,0BAA0B;IACpC,OAAO,EAAE,uBAAuB;IAChC,gCAAgC,EAAE,0BAA0B;IAC5D,aAAa,EAAE,2BAA2B;IAC1C,aAAa,EAAE,8BAA8B;IAC7C,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,iBAAiB;IAC1B,SAAS,EAAE,kBAAkB;IAC7B,SAAS,EAAE,oBAAoB;IAC/B,SAAS,EAAE,oBAAoB;CAC/B,CAAC;AAEF,MAAM,iBAAiB,GAAG;IACzB,WAAW;IACX,cAAc;IACd,0BAA0B,EAAE,eAAe;IAC3C,iBAAiB,EAAE,yBAAyB;IAC5C,oBAAoB;CACpB,CAAC;AAEF,MAAM,qBAAqB,GAAG;IAC7B,cAAc;IACd,cAAc;IACd,sBAAsB;IACtB,kBAAkB;CAClB,CAAC;AAcF;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAC9B,GAAW,EACX,UAAgC,EAAE;IAElC,+BAA+B;IAC/B,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,sBAAsB,EAAE,CAAC;IAC9D,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAEjC,+CAA+C;QAC/C,MAAM,WAAW,GAChB,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,KAAK,CAAC;QAE5E,6DAA6D;QAC7D,IAAI,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;YAC3C,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACzE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;QACtC,CAAC;QAED,iDAAiD;QACjD,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACxD,OAAO;gBACN,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,kDAAkD;aACzD,CAAC;QACH,CAAC;QAED,6BAA6B;QAC7B,KAAK,MAAM,OAAO,IAAI,mBAAmB,EAAE,CAAC;YAC3C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC5B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;YACxE,CAAC;QACF,CAAC;QAED,qCAAqC;QACrC,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;YACzC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAChC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC/B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;gBAChE,CAAC;YACF,CAAC;iBAAM,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC;gBACrE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;YAChE,CAAC;QACF,CAAC;QAED,uEAAuE;QACvE,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,qBAAqB,CAAC,IAAI,CAC3C,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,MAAM,CAAC,CAClE,CAAC;YACF,IAAI,CAAC,SAAS,EAAE,CAAC;gBAChB,OAAO;oBACN,KAAK,EAAE,KAAK;oBACZ,KAAK,EAAE,gFAAgF;iBACvF,CAAC;YACH,CAAC;QACF,CAAC;QAED,wCAAwC;QACxC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAEzE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IACtD,CAAC;AACF,CAAC;AAaD,MAAM,cAAc,GAAkB;IACrC,cAAc,EAAE,GAAG,GAAG,IAAI,EAAE,QAAQ;IACpC,eAAe,EAAE,GAAG;IACpB,qBAAqB,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO;IACzC,oBAAoB,EAAE,GAAG,GAAG,IAAI,EAAE,QAAQ;CAC1C,CAAC;AAmMO,wCAAc;AArLvB;;GAEG;AACH,SAAgB,kBAAkB,CACjC,MAAc,EACd,SAAiC,EAAE;IAEnC,MAAM,eAAe,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,EAAE,CAAC;IACzD,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,MAAM,CAAC,CAAC;IAE9B,IAAI,KAAK,GAAG,eAAe,CAAC,cAAc,EAAE,CAAC;QAC5C,OAAO;YACN,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,gBAAgB,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,gCAAgC,IAAI,CAAC,KAAK,CACxF,eAAe,CAAC,cAAc,GAAG,IAAI,CACrC,KAAK;YACN,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE;SAC/B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC;AACzD,CAAC;AAED,+CAA+C;AAC/C,wBAAwB;AACxB,+CAA+C;AAE/C,sFAAsF;AACtF,gGAAgG;AAChG,MAAM,kBAAkB,GAAG;IAC1B,qCAAqC,EAAE,WAAW;IAClD,4BAA4B,EAAE,gBAAgB;IAC9C,kCAAkC,EAAE,oBAAoB;IACxD,gBAAgB,EAAE,eAAe;IACjC,qBAAqB,EAAE,yBAAyB;CAChD,CAAC;AAEF,MAAM,cAAc,GAA2B;IAC9C,mBAAmB,EAAE,mDAAmD;IACxE,YAAY,EAAE,2DAA2D;IACzE,SAAS,EACR,gIAAgI;IACjI,aAAa,EAAE,uCAAuC;IACtD,cAAc,EAAE,qDAAqD;IACrE,OAAO,EAAE,gDAAgD;IACzD,eAAe,EAAE,qDAAqD;IACtE,oBAAoB,EAAE,0DAA0D;IAChF,uBAAuB,EAAE,mDAAmD;IAC5E,eAAe,EAAE,gDAAgD;CACjE,CAAC;AASF;;GAEG;AACH,SAAgB,gBAAgB,CAAC,KAAc;IAC9C,IAAI,OAAO,GAAG,8BAA8B,CAAC;IAC7C,IAAI,IAAwB,CAAC;IAC7B,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,UAA8B,CAAC;IAEnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjD,MAAM,GAAG,GAAG,KAAgC,CAAC;QAE7C,iFAAiF;QACjF,4FAA4F;QAC5F,MAAM,MAAM,GACX,GAAG,CAAC,KAAK,IAAI,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,KAAiC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjG,MAAM,OAAO,GAAG,MAAM,EAAE,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvF,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QACzD,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,OAAO,IAAI,GAAG,CAAC,OAAO,CAAY,CAAC;QAE/D,IAAI,IAAI,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;aAAM,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,EAAE,CAAC;YACzD,OAAO,GAAG,UAAU,CAAC;QACtB,CAAC;QAED,IAAI,IAAI,KAAK,qBAAqB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC1D,SAAS,GAAG,IAAI,CAAC;YACjB,UAAU,GAAI,GAAG,CAAC,UAAqB,IAAI,EAAE,CAAC;QAC/C,CAAC;QAED,kFAAkF;QAClF,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI;YAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC5D,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;YAC1C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAClD,CAAC;IACF,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,GAAG,KAAK,CAAC;QAChB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI;YAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC5D,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;YAC1C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAClD,CAAC;IACF,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QAC1B,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAC7C,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AACjD,CAAC;AAED,+CAA+C;AAC/C,wBAAwB;AACxB,+CAA+C;AAE/C,MAAM,eAAe,GAAG,wCAAwC,CAAC;AAQjE;;GAEG;AACH,SAAgB,oBAAoB,CAAC,MAAc;IAClD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAE9B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,OAAO;YACN,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,6EAA6E;SACpF,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC;QAClD,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC;YAChC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,KAAK,CAAC;IAEV,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AACrC,CAAC;AAYD;;;GAGG;AACH,SAAgB,WAAW,CAC1B,OAAe,EACf,SAAiB,EACjB,SAAkB;IAElB,8DAA8D;IAC9D,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,aAAa,CAAiC,CAAC;IAC3F,MAAM,EAAE,GAAG,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,GAAG,EAAE,IAAI,KAAK,IAAI,OAAO,EAAE,CAAC;IAE5C,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhF,OAAO;QACN,mBAAmB,EAAE,UAAU,SAAS,EAAE;QAC1C,mBAAmB,EAAE,EAAE,CAAC,QAAQ,EAAE;QAClC,eAAe,EAAE,KAAK;KACtB,CAAC;AACH,CAAC"}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser de Server-Sent Events. Converte o corpo (ReadableStream) num async iterator de
|
|
3
|
+
* eventos `{ event, data }`, com `data` já parseado de JSON quando possível.
|
|
4
|
+
*
|
|
5
|
+
* Robustez:
|
|
6
|
+
* - limites de tamanho (maxEventBytes / maxTotalBytes) → aborta com `protocol` (anti-DoS de memória).
|
|
7
|
+
* - parse incremental O(n) (sem replace global no buffer acumulado).
|
|
8
|
+
* - `reader.cancel()` no finally → fecha a conexão mesmo em `break`/erro (sem socket vazado).
|
|
9
|
+
*
|
|
10
|
+
* @module sse
|
|
11
|
+
*/
|
|
12
|
+
export interface SseEvent {
|
|
13
|
+
event: string;
|
|
14
|
+
data: unknown;
|
|
15
|
+
rawData: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ParseSseOptions {
|
|
18
|
+
/** Signal do chamador, para checagem de abort entre leituras. */
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
/** Traduz uma rejeição de read()/abort para o erro correto (timeout/aborted/network). */
|
|
21
|
+
onAbortError?: () => Error;
|
|
22
|
+
/** Chamado no finally (libera deadline/recursos). */
|
|
23
|
+
onEnd?: () => void;
|
|
24
|
+
maxEventBytes?: number;
|
|
25
|
+
maxTotalBytes?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function parseSse(response: Response, opts?: ParseSseOptions): AsyncGenerator<SseEvent>;
|