Skip to content

Commit 8e22c75

Browse files
authored
Merge pull request #15 from onkernel/release-please--branches--main--changes--next--components--sdk
release: 0.1.0-alpha.13
2 parents 5f3a43d + b6bb901 commit 8e22c75

File tree

21 files changed

+1043
-14
lines changed

21 files changed

+1043
-14
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.1.0-alpha.12"
2+
".": "0.1.0-alpha.13"
33
}

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 5
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-1fe396b957ced73281fc0a61a69b630836aa5c89a8dccce2c5a1716bc9775e80.yml
3-
openapi_spec_hash: 9a0d67fb0781be034b77839584109638
4-
config_hash: df889df131f7438197abd59faace3c77
1+
configured_endpoints: 7
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-c9d64df733f286f09d2203f4e3d820ce57e8d4c629c5e2db4e2bfac91fbc1598.yml
3+
openapi_spec_hash: fa407611fc566d55f403864fbfaa6c23
4+
config_hash: 7f67c5b95af1e4b39525515240b72275

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 0.1.0-alpha.13 (2025-05-19)
4+
5+
Full Changelog: [v0.1.0-alpha.12...v0.1.0-alpha.13](https://github.com/onkernel/kernel-node-sdk/compare/v0.1.0-alpha.12...v0.1.0-alpha.13)
6+
7+
### Features
8+
9+
* **api:** update via SDK Studio ([bba7f08](https://github.com/onkernel/kernel-node-sdk/commit/bba7f08386eb92c2fcc5087a414e6d29f2ece821))
10+
* **api:** update via SDK Studio ([4c42d7c](https://github.com/onkernel/kernel-node-sdk/commit/4c42d7cdd5c0d25d48b2c5ea4bb1db9af009b279))
11+
312
## 0.1.0-alpha.12 (2025-05-19)
413

514
Full Changelog: [v0.1.0-alpha.11...v0.1.0-alpha.12](https://github.com/onkernel/kernel-node-sdk/compare/v0.1.0-alpha.11...v0.1.0-alpha.12)

api.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
# Apps
22

3+
Types:
4+
5+
- <code><a href="./src/resources/apps/apps.ts">AppListResponse</a></code>
6+
7+
Methods:
8+
9+
- <code title="get /apps">client.apps.<a href="./src/resources/apps/apps.ts">list</a>({ ...params }) -> AppListResponse</code>
10+
311
## Deployments
412

513
Types:
614

715
- <code><a href="./src/resources/apps/deployments.ts">DeploymentCreateResponse</a></code>
16+
- <code><a href="./src/resources/apps/deployments.ts">DeploymentFollowResponse</a></code>
817

918
Methods:
1019

1120
- <code title="post /deploy">client.apps.deployments.<a href="./src/resources/apps/deployments.ts">create</a>({ ...params }) -> DeploymentCreateResponse</code>
21+
- <code title="get /apps/{id}/events">client.apps.deployments.<a href="./src/resources/apps/deployments.ts">follow</a>(id) -> DeploymentFollowResponse</code>
1222

1323
## Invocations
1424

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@onkernel/sdk",
3-
"version": "0.1.0-alpha.12",
3+
"version": "0.1.0-alpha.13",
44
"description": "The official TypeScript library for the Kernel API",
55
"author": "Kernel <>",
66
"types": "dist/index.d.ts",

src/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { readEnv } from './internal/utils/env';
3030
import { formatRequestDetails, loggerFor } from './internal/utils/log';
3131
import { isEmptyObj } from './internal/utils/values';
3232
import { KernelApp } from './core/app-framework';
33-
import { Apps } from './resources/apps/apps';
33+
import { AppListParams, AppListResponse, Apps } from './resources/apps/apps';
3434

3535
const environments = {
3636
production: 'https://api.onkernel.com/',
@@ -740,7 +740,7 @@ Kernel.Browsers = Browsers;
740740
export declare namespace Kernel {
741741
export type RequestOptions = Opts.RequestOptions;
742742

743-
export { Apps as Apps };
743+
export { Apps as Apps, type AppListResponse as AppListResponse, type AppListParams as AppListParams };
744744

745745
export {
746746
Browsers as Browsers,

src/core/app-framework.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export interface KernelContext {
2-
invocationId: string;
2+
invocation_id: string;
33
}
44

55
export interface KernelAction {

src/core/streaming.ts

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
import { KernelError } from './error';
2+
import { type ReadableStream } from '../internal/shim-types';
3+
import { makeReadableStream } from '../internal/shims';
4+
import { findDoubleNewlineIndex, LineDecoder } from '../internal/decoders/line';
5+
import { ReadableStreamToAsyncIterable } from '../internal/shims';
6+
import { isAbortError } from '../internal/errors';
7+
import { encodeUTF8 } from '../internal/utils/bytes';
8+
9+
type Bytes = string | ArrayBuffer | Uint8Array | null | undefined;
10+
11+
export type ServerSentEvent = {
12+
event: string | null;
13+
data: string;
14+
raw: string[];
15+
};
16+
17+
export class Stream<Item> implements AsyncIterable<Item> {
18+
controller: AbortController;
19+
20+
constructor(
21+
private iterator: () => AsyncIterator<Item>,
22+
controller: AbortController,
23+
) {
24+
this.controller = controller;
25+
}
26+
27+
static fromSSEResponse<Item>(response: Response, controller: AbortController): Stream<Item> {
28+
let consumed = false;
29+
30+
async function* iterator(): AsyncIterator<Item, any, undefined> {
31+
if (consumed) {
32+
throw new KernelError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
33+
}
34+
consumed = true;
35+
let done = false;
36+
try {
37+
for await (const sse of _iterSSEMessages(response, controller)) {
38+
try {
39+
yield JSON.parse(sse.data);
40+
} catch (e) {
41+
console.error(`Could not parse message into JSON:`, sse.data);
42+
console.error(`From chunk:`, sse.raw);
43+
throw e;
44+
}
45+
}
46+
done = true;
47+
} catch (e) {
48+
// If the user calls `stream.controller.abort()`, we should exit without throwing.
49+
if (isAbortError(e)) return;
50+
throw e;
51+
} finally {
52+
// If the user `break`s, abort the ongoing request.
53+
if (!done) controller.abort();
54+
}
55+
}
56+
57+
return new Stream(iterator, controller);
58+
}
59+
60+
/**
61+
* Generates a Stream from a newline-separated ReadableStream
62+
* where each item is a JSON value.
63+
*/
64+
static fromReadableStream<Item>(readableStream: ReadableStream, controller: AbortController): Stream<Item> {
65+
let consumed = false;
66+
67+
async function* iterLines(): AsyncGenerator<string, void, unknown> {
68+
const lineDecoder = new LineDecoder();
69+
70+
const iter = ReadableStreamToAsyncIterable<Bytes>(readableStream);
71+
for await (const chunk of iter) {
72+
for (const line of lineDecoder.decode(chunk)) {
73+
yield line;
74+
}
75+
}
76+
77+
for (const line of lineDecoder.flush()) {
78+
yield line;
79+
}
80+
}
81+
82+
async function* iterator(): AsyncIterator<Item, any, undefined> {
83+
if (consumed) {
84+
throw new KernelError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
85+
}
86+
consumed = true;
87+
let done = false;
88+
try {
89+
for await (const line of iterLines()) {
90+
if (done) continue;
91+
if (line) yield JSON.parse(line);
92+
}
93+
done = true;
94+
} catch (e) {
95+
// If the user calls `stream.controller.abort()`, we should exit without throwing.
96+
if (isAbortError(e)) return;
97+
throw e;
98+
} finally {
99+
// If the user `break`s, abort the ongoing request.
100+
if (!done) controller.abort();
101+
}
102+
}
103+
104+
return new Stream(iterator, controller);
105+
}
106+
107+
[Symbol.asyncIterator](): AsyncIterator<Item> {
108+
return this.iterator();
109+
}
110+
111+
/**
112+
* Splits the stream into two streams which can be
113+
* independently read from at different speeds.
114+
*/
115+
tee(): [Stream<Item>, Stream<Item>] {
116+
const left: Array<Promise<IteratorResult<Item>>> = [];
117+
const right: Array<Promise<IteratorResult<Item>>> = [];
118+
const iterator = this.iterator();
119+
120+
const teeIterator = (queue: Array<Promise<IteratorResult<Item>>>): AsyncIterator<Item> => {
121+
return {
122+
next: () => {
123+
if (queue.length === 0) {
124+
const result = iterator.next();
125+
left.push(result);
126+
right.push(result);
127+
}
128+
return queue.shift()!;
129+
},
130+
};
131+
};
132+
133+
return [
134+
new Stream(() => teeIterator(left), this.controller),
135+
new Stream(() => teeIterator(right), this.controller),
136+
];
137+
}
138+
139+
/**
140+
* Converts this stream to a newline-separated ReadableStream of
141+
* JSON stringified values in the stream
142+
* which can be turned back into a Stream with `Stream.fromReadableStream()`.
143+
*/
144+
toReadableStream(): ReadableStream {
145+
const self = this;
146+
let iter: AsyncIterator<Item>;
147+
148+
return makeReadableStream({
149+
async start() {
150+
iter = self[Symbol.asyncIterator]();
151+
},
152+
async pull(ctrl: any) {
153+
try {
154+
const { value, done } = await iter.next();
155+
if (done) return ctrl.close();
156+
157+
const bytes = encodeUTF8(JSON.stringify(value) + '\n');
158+
159+
ctrl.enqueue(bytes);
160+
} catch (err) {
161+
ctrl.error(err);
162+
}
163+
},
164+
async cancel() {
165+
await iter.return?.();
166+
},
167+
});
168+
}
169+
}
170+
171+
export async function* _iterSSEMessages(
172+
response: Response,
173+
controller: AbortController,
174+
): AsyncGenerator<ServerSentEvent, void, unknown> {
175+
if (!response.body) {
176+
controller.abort();
177+
if (
178+
typeof (globalThis as any).navigator !== 'undefined' &&
179+
(globalThis as any).navigator.product === 'ReactNative'
180+
) {
181+
throw new KernelError(
182+
`The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`,
183+
);
184+
}
185+
throw new KernelError(`Attempted to iterate over a response with no body`);
186+
}
187+
188+
const sseDecoder = new SSEDecoder();
189+
const lineDecoder = new LineDecoder();
190+
191+
const iter = ReadableStreamToAsyncIterable<Bytes>(response.body);
192+
for await (const sseChunk of iterSSEChunks(iter)) {
193+
for (const line of lineDecoder.decode(sseChunk)) {
194+
const sse = sseDecoder.decode(line);
195+
if (sse) yield sse;
196+
}
197+
}
198+
199+
for (const line of lineDecoder.flush()) {
200+
const sse = sseDecoder.decode(line);
201+
if (sse) yield sse;
202+
}
203+
}
204+
205+
/**
206+
* Given an async iterable iterator, iterates over it and yields full
207+
* SSE chunks, i.e. yields when a double new-line is encountered.
208+
*/
209+
async function* iterSSEChunks(iterator: AsyncIterableIterator<Bytes>): AsyncGenerator<Uint8Array> {
210+
let data = new Uint8Array();
211+
212+
for await (const chunk of iterator) {
213+
if (chunk == null) {
214+
continue;
215+
}
216+
217+
const binaryChunk =
218+
chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
219+
: typeof chunk === 'string' ? encodeUTF8(chunk)
220+
: chunk;
221+
222+
let newData = new Uint8Array(data.length + binaryChunk.length);
223+
newData.set(data);
224+
newData.set(binaryChunk, data.length);
225+
data = newData;
226+
227+
let patternIndex;
228+
while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) {
229+
yield data.slice(0, patternIndex);
230+
data = data.slice(patternIndex);
231+
}
232+
}
233+
234+
if (data.length > 0) {
235+
yield data;
236+
}
237+
}
238+
239+
class SSEDecoder {
240+
private data: string[];
241+
private event: string | null;
242+
private chunks: string[];
243+
244+
constructor() {
245+
this.event = null;
246+
this.data = [];
247+
this.chunks = [];
248+
}
249+
250+
decode(line: string) {
251+
if (line.endsWith('\r')) {
252+
line = line.substring(0, line.length - 1);
253+
}
254+
255+
if (!line) {
256+
// empty line and we didn't previously encounter any messages
257+
if (!this.event && !this.data.length) return null;
258+
259+
const sse: ServerSentEvent = {
260+
event: this.event,
261+
data: this.data.join('\n'),
262+
raw: this.chunks,
263+
};
264+
265+
this.event = null;
266+
this.data = [];
267+
this.chunks = [];
268+
269+
return sse;
270+
}
271+
272+
this.chunks.push(line);
273+
274+
if (line.startsWith(':')) {
275+
return null;
276+
}
277+
278+
let [fieldname, _, value] = partition(line, ':');
279+
280+
if (value.startsWith(' ')) {
281+
value = value.substring(1);
282+
}
283+
284+
if (fieldname === 'event') {
285+
this.event = value;
286+
} else if (fieldname === 'data') {
287+
this.data.push(value);
288+
}
289+
290+
return null;
291+
}
292+
}
293+
294+
function partition(str: string, delimiter: string): [string, string, string] {
295+
const index = str.indexOf(delimiter);
296+
if (index !== -1) {
297+
return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)];
298+
}
299+
300+
return [str, '', ''];
301+
}

0 commit comments

Comments
 (0)