@msg91comm/m91 0.0.0-stage → 1.2.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/CHANGELOG.md +94 -0
- package/LICENSE +21 -0
- package/README.md +647 -2
- package/dist/src/client.d.ts +165 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +221 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/exceptions.d.ts +114 -0
- package/dist/src/exceptions.d.ts.map +1 -0
- package/dist/src/exceptions.js +199 -0
- package/dist/src/exceptions.js.map +1 -0
- package/dist/src/http.d.ts +71 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +150 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +49 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +45 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/integrations/claude-agent-sdk.d.ts +63 -0
- package/dist/src/integrations/claude-agent-sdk.d.ts.map +1 -0
- package/dist/src/integrations/claude-agent-sdk.js +109 -0
- package/dist/src/integrations/claude-agent-sdk.js.map +1 -0
- package/dist/src/integrations/google-adk.d.ts +47 -0
- package/dist/src/integrations/google-adk.d.ts.map +1 -0
- package/dist/src/integrations/google-adk.js +84 -0
- package/dist/src/integrations/google-adk.js.map +1 -0
- package/dist/src/integrations/index.d.ts +26 -0
- package/dist/src/integrations/index.d.ts.map +1 -0
- package/dist/src/integrations/index.js +25 -0
- package/dist/src/integrations/index.js.map +1 -0
- package/dist/src/integrations/langchain.d.ts +94 -0
- package/dist/src/integrations/langchain.d.ts.map +1 -0
- package/dist/src/integrations/langchain.js +129 -0
- package/dist/src/integrations/langchain.js.map +1 -0
- package/dist/src/integrations/langgraph.d.ts +79 -0
- package/dist/src/integrations/langgraph.d.ts.map +1 -0
- package/dist/src/integrations/langgraph.js +114 -0
- package/dist/src/integrations/langgraph.js.map +1 -0
- package/dist/src/integrations/mastra.d.ts +75 -0
- package/dist/src/integrations/mastra.d.ts.map +1 -0
- package/dist/src/integrations/mastra.js +109 -0
- package/dist/src/integrations/mastra.js.map +1 -0
- package/dist/src/integrations/openai-agents.d.ts +61 -0
- package/dist/src/integrations/openai-agents.d.ts.map +1 -0
- package/dist/src/integrations/openai-agents.js +127 -0
- package/dist/src/integrations/openai-agents.js.map +1 -0
- package/dist/src/integrations/specs.d.ts +173 -0
- package/dist/src/integrations/specs.d.ts.map +1 -0
- package/dist/src/integrations/specs.js +217 -0
- package/dist/src/integrations/specs.js.map +1 -0
- package/dist/src/integrations/vercel-ai.d.ts +74 -0
- package/dist/src/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/src/integrations/vercel-ai.js +102 -0
- package/dist/src/integrations/vercel-ai.js.map +1 -0
- package/dist/src/integrations/zod-shapes.d.ts +19 -0
- package/dist/src/integrations/zod-shapes.d.ts.map +1 -0
- package/dist/src/integrations/zod-shapes.js +44 -0
- package/dist/src/integrations/zod-shapes.js.map +1 -0
- package/dist/src/models.d.ts +274 -0
- package/dist/src/models.d.ts.map +1 -0
- package/dist/src/models.js +383 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/platform.d.ts +176 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +286 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/url-builder.d.ts +65 -0
- package/dist/src/url-builder.d.ts.map +1 -0
- package/dist/src/url-builder.js +167 -0
- package/dist/src/url-builder.js.map +1 -0
- package/examples/approval-gate.ts +35 -0
- package/examples/basic-alert.ts +53 -0
- package/examples/claude-agent-sdk.ts +23 -0
- package/examples/deep-agents.ts +31 -0
- package/examples/google-adk.ts +39 -0
- package/examples/langchain-agent.ts +65 -0
- package/examples/langgraph-hitl.ts +70 -0
- package/examples/mastra.ts +41 -0
- package/examples/openai-agents.ts +33 -0
- package/examples/vercel-ai-agent.ts +34 -0
- package/examples/webhook-urls.ts +44 -0
- package/package.json +135 -4
- package/src/client.ts +324 -0
- package/src/exceptions.ts +229 -0
- package/src/http.ts +174 -0
- package/src/index.ts +91 -0
- package/src/integrations/claude-agent-sdk.ts +158 -0
- package/src/integrations/google-adk.ts +122 -0
- package/src/integrations/index.ts +27 -0
- package/src/integrations/langchain.ts +207 -0
- package/src/integrations/langgraph.ts +142 -0
- package/src/integrations/mastra.ts +178 -0
- package/src/integrations/openai-agents.ts +173 -0
- package/src/integrations/specs.ts +328 -0
- package/src/integrations/vercel-ai.ts +151 -0
- package/src/integrations/zod-shapes.ts +55 -0
- package/src/models.ts +660 -0
- package/src/platform.ts +343 -0
- package/src/url-builder.ts +225 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every failure the SDK can throw, and the one place an HTTP response becomes one.
|
|
3
|
+
*
|
|
4
|
+
* Branch on `.code` — a stable string the API contract pins down — never on
|
|
5
|
+
* `.message`, which is written for a person reading a terminal and is expected
|
|
6
|
+
* to change.
|
|
7
|
+
*/
|
|
8
|
+
export class M91Error extends Error {
|
|
9
|
+
code;
|
|
10
|
+
statusCode;
|
|
11
|
+
details;
|
|
12
|
+
constructor(message, code = 'M91_ERROR', statusCode, details) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = new.target.name;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.statusCode = statusCode;
|
|
17
|
+
this.details = details;
|
|
18
|
+
// Without this, `err instanceof M91ValidationError` is false when the
|
|
19
|
+
// package is compiled down to ES5 by a consumer's bundler.
|
|
20
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 400/413. The request was wrong, and it is never retried.
|
|
25
|
+
*
|
|
26
|
+
* Also thrown locally, before any HTTP call, when a field breaks a documented
|
|
27
|
+
* limit. Every schema server-side is `.strict()`, so a mistyped field name is a
|
|
28
|
+
* 400 rather than a silent drop — and a round trip to learn that is a round
|
|
29
|
+
* trip wasted.
|
|
30
|
+
*/
|
|
31
|
+
export class M91ValidationError extends M91Error {
|
|
32
|
+
constructor(message, details, statusCode = 400, code = 'VALIDATION_ERROR') {
|
|
33
|
+
super(message, code, statusCode, details);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** 401. Credential invalid, expired or revoked — nothing is cached server-side. */
|
|
37
|
+
export class M91AuthError extends M91Error {
|
|
38
|
+
constructor(message = 'Unauthorized', details) {
|
|
39
|
+
super(message, 'UNAUTHORIZED', 401, details);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** 403. Authenticated, but not allowed. On `close()` this means success. */
|
|
43
|
+
export class M91ForbiddenError extends M91Error {
|
|
44
|
+
constructor(message = 'Forbidden', details) {
|
|
45
|
+
super(message, 'FORBIDDEN', 403, details);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 404. Absent — **or present and not yours**. The API answers the same way to
|
|
50
|
+
* both on purpose, so an id cannot become an oracle for what exists.
|
|
51
|
+
*/
|
|
52
|
+
export class M91NotFoundError extends M91Error {
|
|
53
|
+
constructor(message = 'Not found', details) {
|
|
54
|
+
super(message, 'NOT_FOUND', 404, details);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 409. The channel has nobody who can actually be woken.
|
|
59
|
+
*
|
|
60
|
+
* `unregistered` is the invited numbers with no M91 account — invited, never
|
|
61
|
+
* installed the app, and unreachable. An ordinary SMS would not pierce Do Not
|
|
62
|
+
* Disturb, would not repeat, and would not escalate.
|
|
63
|
+
*/
|
|
64
|
+
export class M91ChannelEmptyError extends M91Error {
|
|
65
|
+
unregistered;
|
|
66
|
+
constructor(message, unregistered = [], details) {
|
|
67
|
+
super(message, 'CHANNEL_EMPTY', 409, details);
|
|
68
|
+
this.unregistered = unregistered;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 429. 60 sends a minute per send link.
|
|
73
|
+
*
|
|
74
|
+
* `retryAfter` always has a value: the server sets `Retry-After` on every 429
|
|
75
|
+
* precisely so a caller does not have to guess, and the usual guess is to retry
|
|
76
|
+
* immediately and forever.
|
|
77
|
+
*/
|
|
78
|
+
export class M91RateLimitError extends M91Error {
|
|
79
|
+
retryAfter;
|
|
80
|
+
constructor(message = 'Rate limited', retryAfter = 60, details) {
|
|
81
|
+
super(message, 'RATE_LIMITED', 429, details);
|
|
82
|
+
this.retryAfter = retryAfter;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** 5xx. Not the caller's fault. Retried with backoff before it reaches you. */
|
|
86
|
+
export class M91ServerError extends M91Error {
|
|
87
|
+
constructor(message = 'Internal server error', statusCode = 500, details) {
|
|
88
|
+
super(message, 'INTERNAL_ERROR', statusCode, details);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The request never got an answer — DNS, TLS, connect or read timeout.
|
|
93
|
+
*
|
|
94
|
+
* Not retried. A send that timed out may or may not have raised an alert, and
|
|
95
|
+
* retrying it blindly is how one condition wakes a team four times.
|
|
96
|
+
*/
|
|
97
|
+
export class M91NetworkError extends M91Error {
|
|
98
|
+
constructor(message = 'Network error', details) {
|
|
99
|
+
super(message, 'NETWORK_ERROR', undefined, details);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The API said 2xx and **nobody's phone rang**.
|
|
104
|
+
*
|
|
105
|
+
* The failure mode integrations actually hit, and it is invisible if you only
|
|
106
|
+
* check the status code. `delivered` counts phones sent to, not the size of the
|
|
107
|
+
* roster: a channel of four whose device tokens are all stale answers 201 with
|
|
108
|
+
* `delivered: 0`.
|
|
109
|
+
*
|
|
110
|
+
* A *deduplicated* send is not this error — `delivered` is 0 because the same
|
|
111
|
+
* alert is already open and already ringing, which is success.
|
|
112
|
+
*/
|
|
113
|
+
export class M91DeliveryError extends M91Error {
|
|
114
|
+
delivered;
|
|
115
|
+
unregistered;
|
|
116
|
+
noDevice;
|
|
117
|
+
pending;
|
|
118
|
+
constructor(message, delivered = 0, unregistered = [], noDevice = 0, pending = 0) {
|
|
119
|
+
super(message, 'DELIVERY_FAILED');
|
|
120
|
+
this.delivered = delivered;
|
|
121
|
+
this.unregistered = unregistered;
|
|
122
|
+
this.noDevice = noDevice;
|
|
123
|
+
this.pending = pending;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** `waitForDecision` ran out of time with no SHARED response. Nobody answered. */
|
|
127
|
+
export class M91TimeoutError extends M91Error {
|
|
128
|
+
customId;
|
|
129
|
+
timeoutMs;
|
|
130
|
+
constructor(customId, timeoutMs) {
|
|
131
|
+
super(`Nobody answered '${customId}' within ${Math.round(timeoutMs / 1000)}s. The alert is still open.`, 'TIMEOUT');
|
|
132
|
+
this.customId = customId;
|
|
133
|
+
this.timeoutMs = timeoutMs;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Turn one HTTP response into either the payload or a thrown error.
|
|
138
|
+
*
|
|
139
|
+
* **Every M91 route answers in an envelope** — `{ success: true, data }` or
|
|
140
|
+
* `{ success: false, error: { code, message, details } }` — and this is the only
|
|
141
|
+
* place the SDK knows that. Everything above this line sees the inner `data`.
|
|
142
|
+
*
|
|
143
|
+
* The error class is chosen by HTTP status rather than by `error.code`, because
|
|
144
|
+
* the status is what the server derives the code from in the first place;
|
|
145
|
+
* keeping them in step in two places is how they drift apart. The body's `code`
|
|
146
|
+
* is still carried onto the error.
|
|
147
|
+
*/
|
|
148
|
+
export function unwrap(status, body, headers) {
|
|
149
|
+
const envelope = (body ?? {});
|
|
150
|
+
const err = envelope.error ?? {};
|
|
151
|
+
const details = err.details;
|
|
152
|
+
const code = err.code;
|
|
153
|
+
if (status >= 200 && status < 300) {
|
|
154
|
+
if (envelope && typeof envelope === 'object' && 'data' in envelope)
|
|
155
|
+
return envelope.data;
|
|
156
|
+
// A route outside the envelope, or a proxy that rewrote the body. Hand it
|
|
157
|
+
// back rather than inventing a failure the server never reported.
|
|
158
|
+
return body;
|
|
159
|
+
}
|
|
160
|
+
const message = err.message || `M91 request failed with HTTP ${status}`;
|
|
161
|
+
if (status === 409 || code === 'CHANNEL_EMPTY') {
|
|
162
|
+
const unregistered = details?.unregistered ?? [];
|
|
163
|
+
throw new M91ChannelEmptyError(message, unregistered, details);
|
|
164
|
+
}
|
|
165
|
+
if (status === 429)
|
|
166
|
+
throw new M91RateLimitError(message, retryAfter(headers) ?? 60, details);
|
|
167
|
+
if (status === 400)
|
|
168
|
+
throw withCode(new M91ValidationError(message, details), code);
|
|
169
|
+
if (status === 413) {
|
|
170
|
+
throw new M91ValidationError(message, details, 413, code || 'PAYLOAD_TOO_LARGE');
|
|
171
|
+
}
|
|
172
|
+
if (status === 401)
|
|
173
|
+
throw withCode(new M91AuthError(message, details), code);
|
|
174
|
+
if (status === 403)
|
|
175
|
+
throw withCode(new M91ForbiddenError(message, details), code);
|
|
176
|
+
if (status === 404)
|
|
177
|
+
throw withCode(new M91NotFoundError(message, details), code);
|
|
178
|
+
if (status >= 500)
|
|
179
|
+
throw new M91ServerError(message, status, details);
|
|
180
|
+
throw new M91Error(message, code || 'M91_ERROR', status, details);
|
|
181
|
+
}
|
|
182
|
+
function withCode(error, code) {
|
|
183
|
+
if (code)
|
|
184
|
+
error.code = code;
|
|
185
|
+
return error;
|
|
186
|
+
}
|
|
187
|
+
/** Seconds from `Retry-After`, or undefined. Header lookups are case-insensitive. */
|
|
188
|
+
export function retryAfter(headers) {
|
|
189
|
+
if (!headers)
|
|
190
|
+
return undefined;
|
|
191
|
+
const raw = typeof headers.get === 'function'
|
|
192
|
+
? headers.get('retry-after')
|
|
193
|
+
: Object.entries(headers).find(([k]) => k.toLowerCase() === 'retry-after')?.[1];
|
|
194
|
+
if (raw === null || raw === undefined)
|
|
195
|
+
return undefined;
|
|
196
|
+
const seconds = Number(String(raw).trim());
|
|
197
|
+
return Number.isFinite(seconds) ? Math.max(0, Math.trunc(seconds)) : undefined;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=exceptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exceptions.js","sourceRoot":"","sources":["../../src/exceptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP on native `fetch`, with the retry policy applied. Zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* The rules, which are the interesting part:
|
|
5
|
+
*
|
|
6
|
+
* - **429 is retried exactly once**, after exactly the delay the server asked
|
|
7
|
+
* for. The send limit is 60/minute per link and every 429 carries
|
|
8
|
+
* `Retry-After: 60`. Retrying a second time would be arguing with it.
|
|
9
|
+
* - **5xx is retried with backoff.** Not the caller's fault, and the alert has
|
|
10
|
+
* not been raised, so repeating it cannot double-page anybody.
|
|
11
|
+
* - **4xx is never retried.** Not transient; the next identical request fails
|
|
12
|
+
* identically.
|
|
13
|
+
* - **A network failure is never retried.** A send that timed out may or may not
|
|
14
|
+
* have raised the alert — the response is what was lost, not necessarily the
|
|
15
|
+
* request. Blind retries are how one condition wakes a team four times.
|
|
16
|
+
*/
|
|
17
|
+
export declare const USER_AGENT = "m91-node/1.0.0";
|
|
18
|
+
/** Delays before the 2nd, 3rd and 4th attempt at a 5xx. Four requests total. */
|
|
19
|
+
export declare const SERVER_BACKOFF: readonly [1000, 2000, 4000];
|
|
20
|
+
export declare const RETRYABLE_STATUSES: Set<number>;
|
|
21
|
+
/**
|
|
22
|
+
* A 429 whose `Retry-After` is longer than this is thrown rather than slept
|
|
23
|
+
* through. The server sends 60; anything far past that is a caller sitting
|
|
24
|
+
* blocked in an alerting path, which is worse than an error it can see.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MAX_RETRY_AFTER_MS = 120000;
|
|
27
|
+
/**
|
|
28
|
+
* Milliseconds to wait before trying again, or `null` to stop and throw.
|
|
29
|
+
* `attempt` is 1-based and counts requests already made.
|
|
30
|
+
*/
|
|
31
|
+
export declare function retryDelay(status: number, attempt: number, retryAfterSeconds?: number): number | null;
|
|
32
|
+
/**
|
|
33
|
+
* Replace a send link's token with `***`, wherever it appears.
|
|
34
|
+
*
|
|
35
|
+
* **The token is the credential and it lives in the path**, so a URL is as
|
|
36
|
+
* sensitive as a password and must never reach a log, an error message or a
|
|
37
|
+
* stack trace intact. The server holds itself to the same rule — its error
|
|
38
|
+
* handler reports the route *pattern*, never `req.path`.
|
|
39
|
+
*
|
|
40
|
+
* This takes free text, not only a URL, and that is the point. Redacting only
|
|
41
|
+
* the URL the SDK interpolates is not enough: a fetch failure can carry the URL
|
|
42
|
+
* in its own message, so a carefully-redacted sentence would arrive with the
|
|
43
|
+
* raw token attached to it by the runtime.
|
|
44
|
+
*
|
|
45
|
+
* Query strings go too: a GET-form send carries the alert title, and titles
|
|
46
|
+
* routinely name customers and hosts.
|
|
47
|
+
*/
|
|
48
|
+
export declare function redact(text: string): string;
|
|
49
|
+
export interface RequestOptions {
|
|
50
|
+
method: string;
|
|
51
|
+
url: string;
|
|
52
|
+
body?: unknown;
|
|
53
|
+
query?: Record<string, string>;
|
|
54
|
+
}
|
|
55
|
+
export interface TransportOptions {
|
|
56
|
+
/** Whole-request budget in milliseconds. Default 15s (5s connect + 10s read). */
|
|
57
|
+
timeoutMs?: number;
|
|
58
|
+
headers?: Record<string, string>;
|
|
59
|
+
/** Swappable so tests need no network and no mocking library. */
|
|
60
|
+
fetchImpl?: typeof fetch;
|
|
61
|
+
sleepImpl?: (ms: number) => Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
export declare class Transport {
|
|
64
|
+
private readonly timeoutMs;
|
|
65
|
+
private readonly headers;
|
|
66
|
+
private readonly fetchImpl;
|
|
67
|
+
private readonly sleepImpl;
|
|
68
|
+
constructor(options?: TransportOptions);
|
|
69
|
+
request({ method, url, body, query }: RequestOptions): Promise<unknown>;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,eAAO,MAAM,UAAU,mBAAmB,CAAC;AAE3C,gFAAgF;AAChF,eAAO,MAAM,cAAc,6BAA8B,CAAC;AAE1D,eAAO,MAAM,kBAAkB,aAAgC,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,SAAU,CAAC;AAE1C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYrG;AASD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAID,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE9C,OAAO,GAAE,gBAAqB;IAepC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;CAsC9E"}
|
package/dist/src/http.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP on native `fetch`, with the retry policy applied. Zero dependencies.
|
|
3
|
+
*
|
|
4
|
+
* The rules, which are the interesting part:
|
|
5
|
+
*
|
|
6
|
+
* - **429 is retried exactly once**, after exactly the delay the server asked
|
|
7
|
+
* for. The send limit is 60/minute per link and every 429 carries
|
|
8
|
+
* `Retry-After: 60`. Retrying a second time would be arguing with it.
|
|
9
|
+
* - **5xx is retried with backoff.** Not the caller's fault, and the alert has
|
|
10
|
+
* not been raised, so repeating it cannot double-page anybody.
|
|
11
|
+
* - **4xx is never retried.** Not transient; the next identical request fails
|
|
12
|
+
* identically.
|
|
13
|
+
* - **A network failure is never retried.** A send that timed out may or may not
|
|
14
|
+
* have raised the alert — the response is what was lost, not necessarily the
|
|
15
|
+
* request. Blind retries are how one condition wakes a team four times.
|
|
16
|
+
*/
|
|
17
|
+
import { M91NetworkError, retryAfter, unwrap } from './exceptions.js';
|
|
18
|
+
export const USER_AGENT = 'm91-node/1.0.0';
|
|
19
|
+
/** Delays before the 2nd, 3rd and 4th attempt at a 5xx. Four requests total. */
|
|
20
|
+
export const SERVER_BACKOFF = [1000, 2000, 4000];
|
|
21
|
+
export const RETRYABLE_STATUSES = new Set([500, 502, 503, 504]);
|
|
22
|
+
/**
|
|
23
|
+
* A 429 whose `Retry-After` is longer than this is thrown rather than slept
|
|
24
|
+
* through. The server sends 60; anything far past that is a caller sitting
|
|
25
|
+
* blocked in an alerting path, which is worse than an error it can see.
|
|
26
|
+
*/
|
|
27
|
+
export const MAX_RETRY_AFTER_MS = 120_000;
|
|
28
|
+
/**
|
|
29
|
+
* Milliseconds to wait before trying again, or `null` to stop and throw.
|
|
30
|
+
* `attempt` is 1-based and counts requests already made.
|
|
31
|
+
*/
|
|
32
|
+
export function retryDelay(status, attempt, retryAfterSeconds) {
|
|
33
|
+
if (status === 429) {
|
|
34
|
+
// Once, and only on the first 429. A second means the window has not
|
|
35
|
+
// actually cleared and the caller needs to hear about it.
|
|
36
|
+
if (attempt >= 2)
|
|
37
|
+
return null;
|
|
38
|
+
const wait = (retryAfterSeconds ?? 60) * 1000;
|
|
39
|
+
return wait > MAX_RETRY_AFTER_MS ? null : Math.max(wait, 0);
|
|
40
|
+
}
|
|
41
|
+
if (RETRYABLE_STATUSES.has(status)) {
|
|
42
|
+
return attempt > SERVER_BACKOFF.length ? null : SERVER_BACKOFF[attempt - 1];
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Matches a send link anywhere — in a bare URL, or buried in a sentence — and
|
|
48
|
+
* takes any query string attached to it. Bounded on whitespace so it stops at
|
|
49
|
+
* the end of the URL rather than eating the rest of the message.
|
|
50
|
+
*/
|
|
51
|
+
const SEND_URL_RE = /(\/s\/)[^\s/?#]+(\?[^\s]*)?/g;
|
|
52
|
+
/**
|
|
53
|
+
* Replace a send link's token with `***`, wherever it appears.
|
|
54
|
+
*
|
|
55
|
+
* **The token is the credential and it lives in the path**, so a URL is as
|
|
56
|
+
* sensitive as a password and must never reach a log, an error message or a
|
|
57
|
+
* stack trace intact. The server holds itself to the same rule — its error
|
|
58
|
+
* handler reports the route *pattern*, never `req.path`.
|
|
59
|
+
*
|
|
60
|
+
* This takes free text, not only a URL, and that is the point. Redacting only
|
|
61
|
+
* the URL the SDK interpolates is not enough: a fetch failure can carry the URL
|
|
62
|
+
* in its own message, so a carefully-redacted sentence would arrive with the
|
|
63
|
+
* raw token attached to it by the runtime.
|
|
64
|
+
*
|
|
65
|
+
* Query strings go too: a GET-form send carries the alert title, and titles
|
|
66
|
+
* routinely name customers and hosts.
|
|
67
|
+
*/
|
|
68
|
+
export function redact(text) {
|
|
69
|
+
if (typeof text !== 'string')
|
|
70
|
+
return text;
|
|
71
|
+
return text.replace(SEND_URL_RE, '$1***');
|
|
72
|
+
}
|
|
73
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
74
|
+
export class Transport {
|
|
75
|
+
timeoutMs;
|
|
76
|
+
headers;
|
|
77
|
+
fetchImpl;
|
|
78
|
+
sleepImpl;
|
|
79
|
+
constructor(options = {}) {
|
|
80
|
+
this.timeoutMs = options.timeoutMs ?? 15_000;
|
|
81
|
+
this.headers = {
|
|
82
|
+
// A GET-form send answers with a rendered HTML page when the caller looks
|
|
83
|
+
// like a browser. That page exists so a person tapping a bookmark sees
|
|
84
|
+
// something other than a wall of JSON — but an SDK is not a person, and
|
|
85
|
+
// has to opt out of it explicitly.
|
|
86
|
+
accept: 'application/json',
|
|
87
|
+
'user-agent': USER_AGENT,
|
|
88
|
+
...options.headers,
|
|
89
|
+
};
|
|
90
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
91
|
+
this.sleepImpl = options.sleepImpl ?? defaultSleep;
|
|
92
|
+
}
|
|
93
|
+
async request({ method, url, body, query }) {
|
|
94
|
+
const target = query ? `${url}?${new URLSearchParams(query).toString()}` : url;
|
|
95
|
+
let attempt = 0;
|
|
96
|
+
for (;;) {
|
|
97
|
+
attempt += 1;
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
100
|
+
let status;
|
|
101
|
+
let headers;
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
const response = await this.fetchImpl(target, {
|
|
105
|
+
method,
|
|
106
|
+
headers: body === undefined
|
|
107
|
+
? this.headers
|
|
108
|
+
: { ...this.headers, 'content-type': 'application/json' },
|
|
109
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
});
|
|
112
|
+
status = response.status;
|
|
113
|
+
headers = response.headers;
|
|
114
|
+
parsed = await readBody(response);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// Redacted AFTER composing, not before: a fetch failure can name the
|
|
118
|
+
// URL in its own message, so masking only our half still ships the token.
|
|
119
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
120
|
+
throw new M91NetworkError(redact(`Could not reach M91 at ${target}: ${reason}`));
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
}
|
|
125
|
+
const delay = retryDelay(status, attempt, retryAfter(headers));
|
|
126
|
+
if (delay === null)
|
|
127
|
+
return unwrap(status, parsed, headers);
|
|
128
|
+
await this.sleepImpl(delay);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The parsed envelope, or something `unwrap` can still turn into an error.
|
|
134
|
+
*
|
|
135
|
+
* A non-JSON body here means a proxy, a load balancer or an HTML error page
|
|
136
|
+
* answered instead of M91. Wrapping the text in an envelope-shaped object keeps
|
|
137
|
+
* all the error construction in one place rather than adding a second path.
|
|
138
|
+
*/
|
|
139
|
+
async function readBody(response) {
|
|
140
|
+
const text = await response.text();
|
|
141
|
+
if (!text)
|
|
142
|
+
return { error: { message: `HTTP ${response.status} with an empty body` } };
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(text);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return { error: { message: text.slice(0, 300) } };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M91 — wake a human when software cannot carry on alone.
|
|
3
|
+
*
|
|
4
|
+
* An alert rings every phone in a channel at full volume, through silent mode
|
|
5
|
+
* and Do Not Disturb, and keeps ringing until somebody answers. HIGH and
|
|
6
|
+
* CRITICAL place a voice call if 45 seconds pass with no answer. Whoever takes
|
|
7
|
+
* it on is recorded, and it clears for everyone else.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { M91Client } from '@msg91comm/m91';
|
|
11
|
+
*
|
|
12
|
+
* const m91 = new M91Client(process.env.M91_SEND_LINK!);
|
|
13
|
+
* await m91.verify(); // raises nothing; names the channel
|
|
14
|
+
* await m91.send('Payments API is down', { severity: 'CRITICAL', customId: 'payments-down' });
|
|
15
|
+
* await m91.close({ customId: 'payments-down' });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* And the reason an agent wants it — a question that blocks until a person
|
|
19
|
+
* answers on their phone:
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* if (await m91.requestApproval('Refund 48,000 on ORD-40122?', { customId: 'refund-ORD-40122' })) {
|
|
23
|
+
* await processRefund();
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Everything here is a convenience over one URL. The send link works from a
|
|
28
|
+
* browser bar, a cron `curl` or an `<img>` tag with no client at all, and
|
|
29
|
+
* `M91URLBuilder` is for exactly those.
|
|
30
|
+
*/
|
|
31
|
+
export { M91Client } from './client.js';
|
|
32
|
+
export type { ApprovalOptions, ClientOptions, SendOptions, WaitOptions } from './client.js';
|
|
33
|
+
export { M91Platform, DEFAULT_BASE_URL } from './platform.js';
|
|
34
|
+
export type { PlatformOptions } from './platform.js';
|
|
35
|
+
export { M91URLBuilder, normalizeSendLink } from './url-builder.js';
|
|
36
|
+
export type { BuildManySpec, BuildUrlOptions } from './url-builder.js';
|
|
37
|
+
export { redact } from './http.js';
|
|
38
|
+
export { M91Error, M91ValidationError, M91AuthError, M91ForbiddenError, M91NotFoundError, M91ChannelEmptyError, M91RateLimitError, M91ServerError, M91NetworkError, M91DeliveryError, M91TimeoutError, } from './exceptions.js';
|
|
39
|
+
export { SEVERITIES, EFFECTS, ESCALATING_SEVERITIES, isApproved, validatePhone, buildAlertPayload, } from './models.js';
|
|
40
|
+
export type { AlertDetail, AlertResponse, AlertStatus, ApiKeyInfo, ChannelInfo, Attachment, Decision, Response, Effect, InviteResult, ProvisionedChannel, Recipient, RecipientState, ResponseOption, Severity, } from './models.js';
|
|
41
|
+
/**
|
|
42
|
+
* Kept in step with package.json by `tests/version.test.ts`, not by hand.
|
|
43
|
+
*
|
|
44
|
+
* ESM has no portable way to read package.json at runtime, so this number is
|
|
45
|
+
* written twice — and it drifted, reporting 1.0.0 while the package shipped as
|
|
46
|
+
* 1.1.0. A test is what makes two copies safe.
|
|
47
|
+
*/
|
|
48
|
+
export declare const VERSION = "1.2.0";
|
|
49
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5F,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,OAAO,EACP,qBAAqB,EACrB,UAAU,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,SAAS,EACT,cAAc,EACd,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M91 — wake a human when software cannot carry on alone.
|
|
3
|
+
*
|
|
4
|
+
* An alert rings every phone in a channel at full volume, through silent mode
|
|
5
|
+
* and Do Not Disturb, and keeps ringing until somebody answers. HIGH and
|
|
6
|
+
* CRITICAL place a voice call if 45 seconds pass with no answer. Whoever takes
|
|
7
|
+
* it on is recorded, and it clears for everyone else.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { M91Client } from '@msg91comm/m91';
|
|
11
|
+
*
|
|
12
|
+
* const m91 = new M91Client(process.env.M91_SEND_LINK!);
|
|
13
|
+
* await m91.verify(); // raises nothing; names the channel
|
|
14
|
+
* await m91.send('Payments API is down', { severity: 'CRITICAL', customId: 'payments-down' });
|
|
15
|
+
* await m91.close({ customId: 'payments-down' });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* And the reason an agent wants it — a question that blocks until a person
|
|
19
|
+
* answers on their phone:
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* if (await m91.requestApproval('Refund 48,000 on ORD-40122?', { customId: 'refund-ORD-40122' })) {
|
|
23
|
+
* await processRefund();
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Everything here is a convenience over one URL. The send link works from a
|
|
28
|
+
* browser bar, a cron `curl` or an `<img>` tag with no client at all, and
|
|
29
|
+
* `M91URLBuilder` is for exactly those.
|
|
30
|
+
*/
|
|
31
|
+
export { M91Client } from './client.js';
|
|
32
|
+
export { M91Platform, DEFAULT_BASE_URL } from './platform.js';
|
|
33
|
+
export { M91URLBuilder, normalizeSendLink } from './url-builder.js';
|
|
34
|
+
export { redact } from './http.js';
|
|
35
|
+
export { M91Error, M91ValidationError, M91AuthError, M91ForbiddenError, M91NotFoundError, M91ChannelEmptyError, M91RateLimitError, M91ServerError, M91NetworkError, M91DeliveryError, M91TimeoutError, } from './exceptions.js';
|
|
36
|
+
export { SEVERITIES, EFFECTS, ESCALATING_SEVERITIES, isApproved, validatePhone, buildAlertPayload, } from './models.js';
|
|
37
|
+
/**
|
|
38
|
+
* Kept in step with package.json by `tests/version.test.ts`, not by hand.
|
|
39
|
+
*
|
|
40
|
+
* ESM has no portable way to read package.json at runtime, so this number is
|
|
41
|
+
* written twice — and it drifted, reporting 1.0.0 while the package shipped as
|
|
42
|
+
* 1.1.0. A test is what makes two copies safe.
|
|
43
|
+
*/
|
|
44
|
+
export const VERSION = '1.2.0';
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGpE,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EACL,QAAQ,EACR,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,OAAO,EACP,qBAAqB,EACrB,UAAU,EACV,aAAa,EACb,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAoBrB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`): M91 as the permission
|
|
3
|
+
* prompt, and M91 as tools.
|
|
4
|
+
*
|
|
5
|
+
* **Permission prompt.** `createM91CanUseTool` is a `canUseTool` callback: the
|
|
6
|
+
* tools you name ring a phone and run only if a person approves. Everything
|
|
7
|
+
* else is allowed through untouched.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
11
|
+
* import { M91Client } from '@msg91comm/m91';
|
|
12
|
+
* import { createM91CanUseTool } from '@msg91comm/m91/integrations/claude-agent-sdk';
|
|
13
|
+
*
|
|
14
|
+
* const client = new M91Client(process.env.M91_SEND_LINK!);
|
|
15
|
+
*
|
|
16
|
+
* for await (const message of query({
|
|
17
|
+
* prompt: 'Clean up the stale feature branches and deploy main.',
|
|
18
|
+
* options: { canUseTool: createM91CanUseTool(client, { tools: ['Bash', 'Write'] }) },
|
|
19
|
+
* })) {
|
|
20
|
+
* if (message.type === 'result') console.log(message);
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* `canUseTool` only runs when the SDK would otherwise ask. To gate a tool
|
|
25
|
+
* whatever the permission mode (including `bypassPermissions`), use the
|
|
26
|
+
* `PreToolUse` hook from `createM91PreToolUseHook` instead.
|
|
27
|
+
*
|
|
28
|
+
* **Tools.** `createM91McpServer` exposes `m91_request_approval` and
|
|
29
|
+
* `m91_send_alert` as an in-process MCP server, for when the agent itself
|
|
30
|
+
* should decide to ask or to wake somebody.
|
|
31
|
+
*/
|
|
32
|
+
import type { CanUseTool, HookCallbackMatcher, McpSdkServerConfigWithInstance } from '@anthropic-ai/claude-agent-sdk';
|
|
33
|
+
import type { M91Client } from '../client.js';
|
|
34
|
+
import { type AlertDefaults, type ApprovalDefaults, type GateOptions } from './specs.js';
|
|
35
|
+
/** Tool names, or a predicate. Required: an agent that rings for every `Read` gets muted. */
|
|
36
|
+
type ClaudeGateOptions = GateOptions & {
|
|
37
|
+
tools: NonNullable<GateOptions['tools']>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A `canUseTool` callback that asks a person on their phone before the tools
|
|
41
|
+
* in `options.tools` run.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createM91CanUseTool(client: M91Client, options: ClaudeGateOptions): CanUseTool;
|
|
44
|
+
/**
|
|
45
|
+
* A `PreToolUse` hook matcher that asks a person before the tools in
|
|
46
|
+
* `options.tools` run, in every permission mode. Pass it as
|
|
47
|
+
* `options.hooks.PreToolUse`.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* query({ prompt, options: { hooks: { PreToolUse: createM91PreToolUseHook(client, { tools: ['Bash'] }) } } })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function createM91PreToolUseHook(client: M91Client, options: ClaudeGateOptions & {
|
|
54
|
+
timeoutSeconds?: number;
|
|
55
|
+
}): HookCallbackMatcher[];
|
|
56
|
+
/**
|
|
57
|
+
* `m91_request_approval` and `m91_send_alert` as an in-process MCP server.
|
|
58
|
+
* Register it under `mcpServers.m91`; the tools appear to Claude as
|
|
59
|
+
* `mcp__m91__m91_request_approval` and `mcp__m91__m91_send_alert`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createM91McpServer(client: M91Client, defaults?: ApprovalDefaults & AlertDefaults): Promise<McpSdkServerConfigWithInstance>;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=claude-agent-sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-agent-sdk.d.ts","sourceRoot":"","sources":["../../../src/integrations/claude-agent-sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,mBAAmB,EACnB,8BAA8B,EAE/B,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAML,KAAK,aAAa,EAElB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAKjB,MAAM,YAAY,CAAC;AAGpB,6FAA6F;AAC7F,KAAK,iBAAiB,GAAG,WAAW,GAAG;IAAE,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;CAAE,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAU7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,iBAAiB,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD,mBAAmB,EAAE,CAyBvB;AAsBD;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,SAAS,EACjB,QAAQ,GAAE,gBAAgB,GAAG,aAAkB,GAC9C,OAAO,CAAC,8BAA8B,CAAC,CAczC"}
|