intentkit-otel 1.0.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 +93 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/middleware.d.ts +18 -0
- package/dist/src/middleware.d.ts.map +1 -0
- package/dist/src/middleware.js +87 -0
- package/dist/src/middleware.js.map +1 -0
- package/dist/src/types.d.ts +13 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +35 -0
- package/src/index.ts +2 -0
- package/src/middleware.ts +115 -0
- package/src/types.ts +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# intentkit-otel
|
|
2
|
+
|
|
3
|
+
OpenTelemetry middleware for IntentKit. Exports function call telemetry as OTLP spans to any compatible backend (DataDog, Grafana Cloud, New Relic, Jaeger, etc.).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install intentkit-otel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { IntentRegistry, serve } from 'intentkit';
|
|
15
|
+
import { createOtelMiddleware } from 'intentkit-otel';
|
|
16
|
+
|
|
17
|
+
const { middleware, shutdown } = createOtelMiddleware({
|
|
18
|
+
endpoint: 'http://localhost:4318/v1/traces',
|
|
19
|
+
serviceName: 'my-agent-service',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const registry = new IntentRegistry()
|
|
23
|
+
.use(middleware)
|
|
24
|
+
.register(/* your functions */);
|
|
25
|
+
|
|
26
|
+
await serve({ registry, context });
|
|
27
|
+
|
|
28
|
+
// On process exit:
|
|
29
|
+
process.on('SIGTERM', async () => {
|
|
30
|
+
await shutdown();
|
|
31
|
+
process.exit(0);
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Span Attributes
|
|
36
|
+
|
|
37
|
+
Every function call produces a span with:
|
|
38
|
+
|
|
39
|
+
| Attribute | Description |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `intentkit.function.name` | Function name (e.g., `create_task`) |
|
|
42
|
+
| `intentkit.caller.role` | Caller's role (e.g., `admin`) |
|
|
43
|
+
| `intentkit.caller.capabilities` | Comma-separated capabilities |
|
|
44
|
+
| `intentkit.request.id` | Unique request UUID |
|
|
45
|
+
| `intentkit.status` | `success` or `error` |
|
|
46
|
+
| `intentkit.duration_ms` | Execution time in milliseconds |
|
|
47
|
+
| `intentkit.events.count` | Number of events emitted |
|
|
48
|
+
| `intentkit.events.names` | Comma-separated event names |
|
|
49
|
+
|
|
50
|
+
## Backend Configuration
|
|
51
|
+
|
|
52
|
+
### DataDog
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
createOtelMiddleware({
|
|
56
|
+
endpoint: 'https://trace.agent.datadoghq.com/v1/traces',
|
|
57
|
+
serviceName: 'my-service',
|
|
58
|
+
headers: { 'DD-API-KEY': process.env.DD_API_KEY! },
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Grafana Cloud
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
createOtelMiddleware({
|
|
66
|
+
endpoint: 'https://otlp-gateway-prod-us-central-0.grafana.net/otlp/v1/traces',
|
|
67
|
+
serviceName: 'my-service',
|
|
68
|
+
headers: { Authorization: `Basic ${btoa(`${instanceId}:${apiKey}`)}` },
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### New Relic
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
createOtelMiddleware({
|
|
76
|
+
endpoint: 'https://otlp.nr-data.net/v1/traces',
|
|
77
|
+
serviceName: 'my-service',
|
|
78
|
+
headers: { 'api-key': process.env.NEW_RELIC_LICENSE_KEY! },
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Local (Jaeger)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
createOtelMiddleware({
|
|
86
|
+
endpoint: 'http://localhost:4318/v1/traces',
|
|
87
|
+
serviceName: 'my-service',
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Middleware } from 'intentkit';
|
|
2
|
+
import type { OtelOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create an OpenTelemetry tracing middleware for IntentKit.
|
|
5
|
+
*
|
|
6
|
+
* Wraps every function call in an OTLP span with attributes for
|
|
7
|
+
* function name, caller role, capabilities, request ID, duration,
|
|
8
|
+
* and emitted events. Exports spans to any OTLP-compatible backend
|
|
9
|
+
* (DataDog, Grafana Cloud, New Relic, Jaeger, etc.).
|
|
10
|
+
*
|
|
11
|
+
* @returns middleware to register via `registry.use()` and a shutdown
|
|
12
|
+
* function to flush pending spans on process exit.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createOtelMiddleware(options: OtelOptions): {
|
|
15
|
+
middleware: Middleware;
|
|
16
|
+
shutdown: () => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAqC,MAAM,WAAW,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG;IAC1D,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CA6FA"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { trace, SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
+
import { BasicTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
3
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
4
|
+
import { Resource } from '@opentelemetry/resources';
|
|
5
|
+
/**
|
|
6
|
+
* Create an OpenTelemetry tracing middleware for IntentKit.
|
|
7
|
+
*
|
|
8
|
+
* Wraps every function call in an OTLP span with attributes for
|
|
9
|
+
* function name, caller role, capabilities, request ID, duration,
|
|
10
|
+
* and emitted events. Exports spans to any OTLP-compatible backend
|
|
11
|
+
* (DataDog, Grafana Cloud, New Relic, Jaeger, etc.).
|
|
12
|
+
*
|
|
13
|
+
* @returns middleware to register via `registry.use()` and a shutdown
|
|
14
|
+
* function to flush pending spans on process exit.
|
|
15
|
+
*/
|
|
16
|
+
export function createOtelMiddleware(options) {
|
|
17
|
+
// --- Setup exporter + provider ---
|
|
18
|
+
const exporter = new OTLPTraceExporter({
|
|
19
|
+
url: options.endpoint,
|
|
20
|
+
headers: options.headers,
|
|
21
|
+
});
|
|
22
|
+
const resourceAttributes = {
|
|
23
|
+
'service.name': options.serviceName,
|
|
24
|
+
...options.attributes,
|
|
25
|
+
};
|
|
26
|
+
const provider = new BasicTracerProvider({
|
|
27
|
+
resource: new Resource(resourceAttributes),
|
|
28
|
+
});
|
|
29
|
+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
|
|
30
|
+
provider.register();
|
|
31
|
+
const tracer = trace.getTracer('intentkit', '1.0.0');
|
|
32
|
+
// --- Build middleware ---
|
|
33
|
+
const middleware = {
|
|
34
|
+
name: 'intentkit-otel',
|
|
35
|
+
execute: async (fn, input, ctx, next) => {
|
|
36
|
+
const span = tracer.startSpan(fn.name);
|
|
37
|
+
// Set base attributes
|
|
38
|
+
span.setAttribute('intentkit.function.name', fn.name);
|
|
39
|
+
span.setAttribute('intentkit.caller.role', ctx.caller.role);
|
|
40
|
+
span.setAttribute('intentkit.caller.capabilities', ctx.caller.capabilities.join(','));
|
|
41
|
+
span.setAttribute('intentkit.request.id', ctx.requestId);
|
|
42
|
+
// Intercept ctx.emit to track emitted events
|
|
43
|
+
const emittedEvents = [];
|
|
44
|
+
const originalEmit = ctx.emit;
|
|
45
|
+
ctx.emit = (eventName, data) => {
|
|
46
|
+
emittedEvents.push(eventName);
|
|
47
|
+
return originalEmit.call(ctx, eventName, data);
|
|
48
|
+
};
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
try {
|
|
51
|
+
const result = await next();
|
|
52
|
+
// Success attributes
|
|
53
|
+
span.setAttribute('intentkit.status', 'success');
|
|
54
|
+
span.setAttribute('intentkit.duration_ms', Date.now() - startTime);
|
|
55
|
+
span.setAttribute('intentkit.events.count', emittedEvents.length);
|
|
56
|
+
span.setAttribute('intentkit.events.names', emittedEvents.join(','));
|
|
57
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
58
|
+
span.end();
|
|
59
|
+
// Restore original emit
|
|
60
|
+
ctx.emit = originalEmit;
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
65
|
+
// Error attributes
|
|
66
|
+
span.setAttribute('intentkit.status', 'error');
|
|
67
|
+
span.setAttribute('intentkit.duration_ms', Date.now() - startTime);
|
|
68
|
+
span.setAttribute('intentkit.events.count', emittedEvents.length);
|
|
69
|
+
span.setAttribute('intentkit.events.names', emittedEvents.join(','));
|
|
70
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
71
|
+
if (error instanceof Error) {
|
|
72
|
+
span.recordException(error);
|
|
73
|
+
}
|
|
74
|
+
span.end();
|
|
75
|
+
// Restore original emit
|
|
76
|
+
ctx.emit = originalEmit;
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
// --- Shutdown flushes pending spans ---
|
|
82
|
+
const shutdown = async () => {
|
|
83
|
+
await provider.shutdown();
|
|
84
|
+
};
|
|
85
|
+
return { middleware, shutdown };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface OtelOptions {
|
|
2
|
+
/** OTLP endpoint URL (e.g., http://localhost:4318/v1/traces) */
|
|
3
|
+
endpoint: string;
|
|
4
|
+
/** Service name for span attributes */
|
|
5
|
+
serviceName: string;
|
|
6
|
+
/** Additional resource attributes */
|
|
7
|
+
attributes?: Record<string, string>;
|
|
8
|
+
/** Export protocol (default: 'http') */
|
|
9
|
+
protocol?: 'http';
|
|
10
|
+
/** Headers to send with OTLP requests (e.g., for auth) */
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "intentkit-otel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenTelemetry middleware for IntentKit — export telemetry to DataDog, Grafana, New Relic",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/src/index.js",
|
|
7
|
+
"types": "dist/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/src/index.js",
|
|
11
|
+
"types": "./dist/src/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist", "src", "README.md"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"intentkit": "^1.0.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@opentelemetry/api": "^1.9.0",
|
|
24
|
+
"@opentelemetry/sdk-trace-base": "^1.30.0",
|
|
25
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.57.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"intentkit": "^1.0.0",
|
|
29
|
+
"typescript": "^5.8.2"
|
|
30
|
+
},
|
|
31
|
+
"engines": { "node": ">=22.0.0" },
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": { "type": "git", "url": "https://github.com/mosoahmed/intentkit" },
|
|
34
|
+
"keywords": ["intentkit", "mcp", "opentelemetry", "otel", "telemetry", "tracing", "ai-agent"]
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { trace, SpanStatusCode, type Tracer } from '@opentelemetry/api';
|
|
2
|
+
import { BasicTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
3
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
4
|
+
import { Resource } from '@opentelemetry/resources';
|
|
5
|
+
import type { Middleware, IntentContext, RegisteredFunction } from 'intentkit';
|
|
6
|
+
import type { OtelOptions } from './types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create an OpenTelemetry tracing middleware for IntentKit.
|
|
10
|
+
*
|
|
11
|
+
* Wraps every function call in an OTLP span with attributes for
|
|
12
|
+
* function name, caller role, capabilities, request ID, duration,
|
|
13
|
+
* and emitted events. Exports spans to any OTLP-compatible backend
|
|
14
|
+
* (DataDog, Grafana Cloud, New Relic, Jaeger, etc.).
|
|
15
|
+
*
|
|
16
|
+
* @returns middleware to register via `registry.use()` and a shutdown
|
|
17
|
+
* function to flush pending spans on process exit.
|
|
18
|
+
*/
|
|
19
|
+
export function createOtelMiddleware(options: OtelOptions): {
|
|
20
|
+
middleware: Middleware;
|
|
21
|
+
shutdown: () => Promise<void>;
|
|
22
|
+
} {
|
|
23
|
+
// --- Setup exporter + provider ---
|
|
24
|
+
const exporter = new OTLPTraceExporter({
|
|
25
|
+
url: options.endpoint,
|
|
26
|
+
headers: options.headers,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const resourceAttributes: Record<string, string> = {
|
|
30
|
+
'service.name': options.serviceName,
|
|
31
|
+
...options.attributes,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const provider = new BasicTracerProvider({
|
|
35
|
+
resource: new Resource(resourceAttributes),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
provider.addSpanProcessor(new SimpleSpanProcessor(exporter));
|
|
39
|
+
provider.register();
|
|
40
|
+
|
|
41
|
+
const tracer: Tracer = trace.getTracer('intentkit', '1.0.0');
|
|
42
|
+
|
|
43
|
+
// --- Build middleware ---
|
|
44
|
+
const middleware: Middleware = {
|
|
45
|
+
name: 'intentkit-otel',
|
|
46
|
+
execute: async (
|
|
47
|
+
fn: RegisteredFunction,
|
|
48
|
+
input: unknown,
|
|
49
|
+
ctx: IntentContext,
|
|
50
|
+
next: () => Promise<unknown>,
|
|
51
|
+
): Promise<unknown> => {
|
|
52
|
+
const span = tracer.startSpan(fn.name);
|
|
53
|
+
|
|
54
|
+
// Set base attributes
|
|
55
|
+
span.setAttribute('intentkit.function.name', fn.name);
|
|
56
|
+
span.setAttribute('intentkit.caller.role', ctx.caller.role);
|
|
57
|
+
span.setAttribute('intentkit.caller.capabilities', ctx.caller.capabilities.join(','));
|
|
58
|
+
span.setAttribute('intentkit.request.id', ctx.requestId);
|
|
59
|
+
|
|
60
|
+
// Intercept ctx.emit to track emitted events
|
|
61
|
+
const emittedEvents: string[] = [];
|
|
62
|
+
const originalEmit = ctx.emit;
|
|
63
|
+
ctx.emit = (eventName: string, data: Record<string, unknown>) => {
|
|
64
|
+
emittedEvents.push(eventName);
|
|
65
|
+
return originalEmit.call(ctx, eventName, data);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const result = await next();
|
|
72
|
+
|
|
73
|
+
// Success attributes
|
|
74
|
+
span.setAttribute('intentkit.status', 'success');
|
|
75
|
+
span.setAttribute('intentkit.duration_ms', Date.now() - startTime);
|
|
76
|
+
span.setAttribute('intentkit.events.count', emittedEvents.length);
|
|
77
|
+
span.setAttribute('intentkit.events.names', emittedEvents.join(','));
|
|
78
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
79
|
+
span.end();
|
|
80
|
+
|
|
81
|
+
// Restore original emit
|
|
82
|
+
ctx.emit = originalEmit;
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
|
|
88
|
+
// Error attributes
|
|
89
|
+
span.setAttribute('intentkit.status', 'error');
|
|
90
|
+
span.setAttribute('intentkit.duration_ms', Date.now() - startTime);
|
|
91
|
+
span.setAttribute('intentkit.events.count', emittedEvents.length);
|
|
92
|
+
span.setAttribute('intentkit.events.names', emittedEvents.join(','));
|
|
93
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
94
|
+
|
|
95
|
+
if (error instanceof Error) {
|
|
96
|
+
span.recordException(error);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
span.end();
|
|
100
|
+
|
|
101
|
+
// Restore original emit
|
|
102
|
+
ctx.emit = originalEmit;
|
|
103
|
+
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// --- Shutdown flushes pending spans ---
|
|
110
|
+
const shutdown = async (): Promise<void> => {
|
|
111
|
+
await provider.shutdown();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return { middleware, shutdown };
|
|
115
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface OtelOptions {
|
|
2
|
+
/** OTLP endpoint URL (e.g., http://localhost:4318/v1/traces) */
|
|
3
|
+
endpoint: string;
|
|
4
|
+
/** Service name for span attributes */
|
|
5
|
+
serviceName: string;
|
|
6
|
+
/** Additional resource attributes */
|
|
7
|
+
attributes?: Record<string, string>;
|
|
8
|
+
/** Export protocol (default: 'http') */
|
|
9
|
+
protocol?: 'http';
|
|
10
|
+
/** Headers to send with OTLP requests (e.g., for auth) */
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
}
|