@kivel.io/sdk 0.8.0 → 0.10.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 +17 -0
- package/README.md +6 -1
- package/dist/index.d.ts +48 -3
- package/dist/index.js +105 -20
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @kivel.io/sdk
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ad5805c: Add live event streams to the SDK and use them in the webhook listener.
|
|
8
|
+
|
|
9
|
+
## 0.9.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- fde4ed4: Show clear messages and structured details for plan allowances and request rate limits.
|
|
14
|
+
- fde4ed4: Type the usage response, including workspace allowance counters and reset times when the server provides them.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- fde4ed4: Read the machine code from the new `code` field while retaining support for older servers. The HTTP `error` field remains a readable message. Exhausted hosted tool, email, and resource allowances return 402. Request rate limits return 429. Timed limits include `Retry-After`, which the SDK and CLI expose to callers.
|
|
19
|
+
|
|
3
20
|
## 0.8.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -24,6 +24,11 @@ await kivel.remove('inboxes', inbox.id);
|
|
|
24
24
|
|
|
25
25
|
`createInbox()` returns a `webhookUrl` that accepts senders without credentials. Reading and waiting still require your workspace key or the private `inspectionToken`. For token-required receiving, pass `{ visibility: 'private' }` and give the sender the returned `accessToken` for its `X-Access-Token` header.
|
|
26
26
|
|
|
27
|
+
|
|
28
|
+
Use `stream(['webhook:' + inbox.id], { token, signal })` to receive change notices
|
|
29
|
+
when live updates are available. Fetch the request bodies with the normal inbox
|
|
30
|
+
methods. Pass the last notice's `cursor` as `lastEventId` when reconnecting.
|
|
31
|
+
|
|
27
32
|
`baseUrl` defaults to `https://kivel.io`; set it to your self-hosted origin.
|
|
28
33
|
Remote origins require HTTPS. The default request timeout is 25 seconds. Requests
|
|
29
34
|
are not automatically retried and redirects are refused.
|
|
@@ -44,7 +49,7 @@ write a local credential file. Do not log responses that contain keys or tokens.
|
|
|
44
49
|
|
|
45
50
|
[Method reference](https://kivel.io/docs/sdk/) ·
|
|
46
51
|
[Agent workflows](https://kivel.io/docs/agent-workflows/) ·
|
|
47
|
-
[Plan limits](https://kivel.io/
|
|
52
|
+
[Plan limits](https://kivel.io/pricing/)
|
|
48
53
|
|
|
49
54
|
Licensed under Apache-2.0. Support: support@kivel.io.
|
|
50
55
|
|
package/dist/index.d.ts
CHANGED
|
@@ -51,13 +51,55 @@ export interface Execution {
|
|
|
51
51
|
resetsAt: string;
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
export interface KivelUsage {
|
|
55
|
+
plan: 'anonymous' | 'free' | 'pro' | 'team';
|
|
56
|
+
limits: Record<string, unknown>;
|
|
57
|
+
runsToday: number;
|
|
58
|
+
resetsAt: string;
|
|
59
|
+
period?: {
|
|
60
|
+
start: string;
|
|
61
|
+
end: string;
|
|
62
|
+
};
|
|
63
|
+
items?: {
|
|
64
|
+
key: string;
|
|
65
|
+
label: string;
|
|
66
|
+
unit: 'count' | 'bytes';
|
|
67
|
+
used: number;
|
|
68
|
+
max: number;
|
|
69
|
+
resetsAt?: string;
|
|
70
|
+
}[];
|
|
71
|
+
}
|
|
72
|
+
export interface KivelLimitDetails {
|
|
73
|
+
code: 'limit_reached' | 'rate_limited';
|
|
74
|
+
limit: string;
|
|
75
|
+
used?: number;
|
|
76
|
+
max?: number;
|
|
77
|
+
plan?: KivelUsage['plan'];
|
|
78
|
+
}
|
|
79
|
+
export interface StreamOptions {
|
|
80
|
+
token?: string;
|
|
81
|
+
lastEventId?: string;
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}
|
|
84
|
+
export type KivelLiveEvent = {
|
|
85
|
+
type: 'ready';
|
|
86
|
+
cursor: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: 'change';
|
|
89
|
+
cursor: string;
|
|
90
|
+
topic: string;
|
|
91
|
+
scopeId: string;
|
|
92
|
+
id: string;
|
|
93
|
+
event: string;
|
|
94
|
+
};
|
|
54
95
|
export declare class KivelError extends Error {
|
|
55
96
|
readonly status: number;
|
|
56
97
|
readonly requestId?: string | undefined;
|
|
57
98
|
readonly retryAfter?: number | undefined;
|
|
58
99
|
readonly requiredPlan?: string | undefined;
|
|
59
100
|
readonly upgradeUrl?: string | undefined;
|
|
60
|
-
|
|
101
|
+
readonly limitDetails?: KivelLimitDetails | undefined;
|
|
102
|
+
constructor(message: string, status: number, requestId?: string | undefined, retryAfter?: number | undefined, requiredPlan?: string | undefined, upgradeUrl?: string | undefined, limitDetails?: KivelLimitDetails | undefined);
|
|
61
103
|
}
|
|
62
104
|
export declare class Kivel {
|
|
63
105
|
private options;
|
|
@@ -66,6 +108,7 @@ export declare class Kivel {
|
|
|
66
108
|
private timeoutMs;
|
|
67
109
|
constructor(options?: ClientOptions);
|
|
68
110
|
request<T = unknown>(method: string, path: string, body?: unknown, resourceToken?: string, headers?: Record<string, string>): Promise<T>;
|
|
111
|
+
stream(topics: string[], options?: StreamOptions): AsyncGenerator<KivelLiveEvent>;
|
|
69
112
|
tools(): Promise<{
|
|
70
113
|
tools: {
|
|
71
114
|
id: string;
|
|
@@ -355,14 +398,16 @@ export declare class Kivel {
|
|
|
355
398
|
inboxDeliveries(id: string, token?: string): Promise<unknown>;
|
|
356
399
|
private waitTimeout;
|
|
357
400
|
private waitQuery;
|
|
358
|
-
usage(): Promise<
|
|
401
|
+
usage(): Promise<KivelUsage>;
|
|
359
402
|
resources(): Promise<unknown>;
|
|
360
403
|
/** Senders can POST to webhookUrl without credentials by default. Inspection stays private.
|
|
361
404
|
* Set visibility to private to require X-Access-Token on incoming webhooks. */
|
|
362
405
|
createInbox(settings?: ResourceSettings): Promise<ResourceCredentials & {
|
|
363
406
|
webhookUrl: string;
|
|
364
407
|
}>;
|
|
365
|
-
inspectInbox(id: string, token?: string): Promise<
|
|
408
|
+
inspectInbox(id: string, token?: string): Promise<{
|
|
409
|
+
requests: CapturedRequest[];
|
|
410
|
+
}>;
|
|
366
411
|
/**
|
|
367
412
|
* Long-polls until the inbox captures a request newer than `after` (any request when omitted).
|
|
368
413
|
* Resolves with `timedOut: true` when nothing arrives; call again to keep waiting.
|
package/dist/index.js
CHANGED
|
@@ -5,16 +5,38 @@ export class KivelError extends Error {
|
|
|
5
5
|
retryAfter;
|
|
6
6
|
requiredPlan;
|
|
7
7
|
upgradeUrl;
|
|
8
|
-
|
|
8
|
+
limitDetails;
|
|
9
|
+
constructor(message, status, requestId, retryAfter, requiredPlan, upgradeUrl, limitDetails) {
|
|
9
10
|
super(message);
|
|
10
11
|
this.status = status;
|
|
11
12
|
this.requestId = requestId;
|
|
12
13
|
this.retryAfter = retryAfter;
|
|
13
14
|
this.requiredPlan = requiredPlan;
|
|
14
15
|
this.upgradeUrl = upgradeUrl;
|
|
16
|
+
this.limitDetails = limitDetails;
|
|
15
17
|
this.name = 'KivelError';
|
|
16
18
|
}
|
|
17
19
|
}
|
|
20
|
+
function apiFailure(response, value) {
|
|
21
|
+
const error = value && typeof value === 'object' ? value : {};
|
|
22
|
+
const code = error.code ?? error.error;
|
|
23
|
+
const details = (code === 'limit_reached' || code === 'rate_limited') && typeof error.limit === 'string'
|
|
24
|
+
? {
|
|
25
|
+
code,
|
|
26
|
+
limit: error.limit,
|
|
27
|
+
...(typeof error.used === 'number' ? { used: error.used } : {}),
|
|
28
|
+
...(typeof error.max === 'number' ? { max: error.max } : {}),
|
|
29
|
+
...(['anonymous', 'free', 'pro', 'team'].some((plan) => plan === error.plan)
|
|
30
|
+
? { plan: error.plan }
|
|
31
|
+
: {}),
|
|
32
|
+
}
|
|
33
|
+
: undefined;
|
|
34
|
+
return new KivelError(typeof error.message === 'string'
|
|
35
|
+
? error.message
|
|
36
|
+
: typeof error.error === 'string'
|
|
37
|
+
? error.error
|
|
38
|
+
: `Request failed (${response.status}).`, response.status, response.headers.get('x-request-id') ?? undefined, Number(response.headers.get('retry-after')) || undefined, typeof error.requiredPlan === 'string' ? error.requiredPlan : undefined, typeof error.upgradeUrl === 'string' ? error.upgradeUrl : undefined, details);
|
|
39
|
+
}
|
|
18
40
|
export class Kivel {
|
|
19
41
|
options;
|
|
20
42
|
baseUrl;
|
|
@@ -84,27 +106,90 @@ export class Kivel {
|
|
|
84
106
|
return text;
|
|
85
107
|
throw new KivelError('The server returned an invalid response.', response.status, response.headers.get('x-request-id') ?? undefined);
|
|
86
108
|
}
|
|
87
|
-
if (!response.ok)
|
|
88
|
-
|
|
89
|
-
typeof value === 'object' &&
|
|
90
|
-
'error' in value &&
|
|
91
|
-
typeof value.error === 'string'
|
|
92
|
-
? value.error
|
|
93
|
-
: `Request failed (${response.status}).`;
|
|
94
|
-
throw new KivelError(message, response.status, response.headers.get('x-request-id') ?? undefined, Number(response.headers.get('retry-after')) || undefined, value &&
|
|
95
|
-
typeof value === 'object' &&
|
|
96
|
-
'requiredPlan' in value &&
|
|
97
|
-
typeof value.requiredPlan === 'string'
|
|
98
|
-
? value.requiredPlan
|
|
99
|
-
: undefined, value &&
|
|
100
|
-
typeof value === 'object' &&
|
|
101
|
-
'upgradeUrl' in value &&
|
|
102
|
-
typeof value.upgradeUrl === 'string'
|
|
103
|
-
? value.upgradeUrl
|
|
104
|
-
: undefined);
|
|
105
|
-
}
|
|
109
|
+
if (!response.ok)
|
|
110
|
+
throw apiFailure(response, value);
|
|
106
111
|
return value;
|
|
107
112
|
}
|
|
113
|
+
async *stream(topics, options = {}) {
|
|
114
|
+
if (!topics.length ||
|
|
115
|
+
topics.length > 10 ||
|
|
116
|
+
topics.some((topic) => !topic || /[,\s]/.test(topic)))
|
|
117
|
+
throw new Error('Choose 1 to 10 live topics.');
|
|
118
|
+
const query = new URLSearchParams({ topics: topics.join(',') });
|
|
119
|
+
const headers = new Headers({ Accept: 'text/event-stream' });
|
|
120
|
+
if (this.options.apiKey)
|
|
121
|
+
headers.set('Authorization', `Bearer ${this.options.apiKey}`);
|
|
122
|
+
if (options.token)
|
|
123
|
+
headers.set('X-Resource-Token', options.token);
|
|
124
|
+
if (options.lastEventId)
|
|
125
|
+
headers.set('Last-Event-ID', options.lastEventId);
|
|
126
|
+
const timeout = AbortSignal.timeout(60_000);
|
|
127
|
+
const response = await this.fetcher(`${this.baseUrl}/api/stream?${query}`, {
|
|
128
|
+
headers,
|
|
129
|
+
redirect: 'error',
|
|
130
|
+
credentials: 'omit',
|
|
131
|
+
signal: options.signal ? AbortSignal.any([timeout, options.signal]) : timeout,
|
|
132
|
+
});
|
|
133
|
+
if (!response.ok)
|
|
134
|
+
throw apiFailure(response, await response.json().catch(() => undefined));
|
|
135
|
+
const invalid = () => new KivelError('The server returned an invalid event stream.', response.status);
|
|
136
|
+
if (!response.body ||
|
|
137
|
+
!response.headers.get('content-type')?.startsWith('text/event-stream'))
|
|
138
|
+
throw invalid();
|
|
139
|
+
const reader = response.body.getReader();
|
|
140
|
+
const decoder = new TextDecoder();
|
|
141
|
+
let pending = '';
|
|
142
|
+
try {
|
|
143
|
+
for (;;) {
|
|
144
|
+
const { value, done } = await reader.read();
|
|
145
|
+
if (done)
|
|
146
|
+
break;
|
|
147
|
+
pending += decoder.decode(value, { stream: true });
|
|
148
|
+
for (let end = /\r?\n\r?\n/.exec(pending); end; end = /\r?\n\r?\n/.exec(pending)) {
|
|
149
|
+
if (end.index > 16_384)
|
|
150
|
+
throw invalid();
|
|
151
|
+
const lines = pending.slice(0, end.index).split(/\r?\n/);
|
|
152
|
+
pending = pending.slice(end.index + end[0].length);
|
|
153
|
+
const cursor = lines.find((line) => line.startsWith('id: '))?.slice(4);
|
|
154
|
+
const type = lines.find((line) => line.startsWith('event: '))?.slice(7);
|
|
155
|
+
if (!cursor || (type !== 'ready' && type !== 'change'))
|
|
156
|
+
continue;
|
|
157
|
+
if (type === 'ready') {
|
|
158
|
+
yield { type, cursor };
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const json = lines
|
|
162
|
+
.filter((line) => line.startsWith('data: '))
|
|
163
|
+
.map((line) => line.slice(6))
|
|
164
|
+
.join('\n');
|
|
165
|
+
let data;
|
|
166
|
+
try {
|
|
167
|
+
data = JSON.parse(json);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
throw invalid();
|
|
171
|
+
}
|
|
172
|
+
if (!data ||
|
|
173
|
+
['topic', 'scopeId', 'id', 'event'].some((key) => typeof data[key] !== 'string'))
|
|
174
|
+
throw invalid();
|
|
175
|
+
yield {
|
|
176
|
+
type,
|
|
177
|
+
cursor,
|
|
178
|
+
topic: data.topic,
|
|
179
|
+
scopeId: data.scopeId,
|
|
180
|
+
id: data.id,
|
|
181
|
+
event: data.event,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (pending.length > 16_384)
|
|
185
|
+
throw invalid();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await reader.cancel().catch(() => { });
|
|
190
|
+
reader.releaseLock();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
108
193
|
tools() {
|
|
109
194
|
return this.request('GET', '/api/tools');
|
|
110
195
|
}
|