@inferencesh/app 0.1.13 → 0.1.14
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 +41 -0
- package/dist/socket.d.ts +4 -0
- package/dist/stream.d.ts +5 -0
- package/dist/stream.js +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -256,6 +256,47 @@ export class App {
|
|
|
256
256
|
|
|
257
257
|
on the wire, a binary frame is one item of the schema's single binary live field (`pcm16(z, rate, channels)` or `media(z, contentType)`); a json text frame is a partial object keyed by field name: an item of a live field (`{"events": {"type": "interrupt"}}`) or a new value for an ordinary field (`{"effect": "robot"}`). frames that do not fit the input schema are answered with `{"error": {"field", "message"}}` and skipped. `live.send({...})` sends the output binary field as a binary frame and everything else as one json frame; unknown output fields throw.
|
|
258
258
|
|
|
259
|
+
### progress from a stream function
|
|
260
|
+
|
|
261
|
+
a stream function may also be an async generator (`async *dictate(input, socket)`). each yield is a progress snapshot of the task's output: it replaces the task's `output` and reaches clients as a task update (sse `/tasks/{id}/stream`), not over the socket — the socket carries the live fields. yield cumulative snapshots; the last yield is the result, so `output_meta` goes there, and a generator that ends without yielding fails the task (`NoOutput`).
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
export const DictateInput = z.object({
|
|
265
|
+
audio: createStreamSchema(z, pcm16(z, 16000)), // mic frames arrive over the socket
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
export const DictateOutput = z.object({
|
|
269
|
+
text: z.string().default(""),
|
|
270
|
+
partial: z.boolean().default(true),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
export class App {
|
|
274
|
+
async *dictate(input, socket) {
|
|
275
|
+
const live = new Live(socket, input, DictateInput, DictateOutput);
|
|
276
|
+
let window = Buffer.alloc(0);
|
|
277
|
+
for await (const { field, value } of live) {
|
|
278
|
+
if (field === "audio") {
|
|
279
|
+
window = Buffer.concat([window, value]);
|
|
280
|
+
if (window.length >= 32000) { // one second of 16 khz s16le
|
|
281
|
+
yield { text: await this.transcribe(window) }; // the transcript so far
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
yield {
|
|
286
|
+
text: await this.transcribe(window),
|
|
287
|
+
partial: false,
|
|
288
|
+
output_meta: { inputs: [], outputs: [] },
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
the client closing the socket ends the `for await`, so the final yield goes after the loop. both channels can be used at once: `live.send({ text })` pushes a partial over the socket for the lowest latency, while the yields build the transcript the task stores.
|
|
295
|
+
|
|
296
|
+
### frames dropped when the app falls behind
|
|
297
|
+
|
|
298
|
+
binary frames are perishable media, so the kernel keeps at most 256 of them queued for the app (about five seconds of 20 ms audio) and drops the oldest when the app reads slower than the client sends — latency stays bounded instead of growing for ever. text frames (commands and patches) are never dropped. `socket.dropped` counts the binary frames lost so far, and `socket.binaryBacklog = null` turns dropping off when every frame matters (a file sent in chunks).
|
|
299
|
+
|
|
259
300
|
## requirements
|
|
260
301
|
|
|
261
302
|
- node.js 18.0.0 or higher
|
package/dist/socket.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export type Frame = Buffer | string;
|
|
|
31
31
|
export interface Socket {
|
|
32
32
|
readonly id: string;
|
|
33
33
|
metadata: Record<string, unknown>;
|
|
34
|
+
/** How many binary frames the kernel has dropped because the app read too slowly. */
|
|
35
|
+
readonly dropped: number;
|
|
36
|
+
/** The most binary frames kept queued (256); null keeps every one of them. */
|
|
37
|
+
binaryBacklog: number | null;
|
|
34
38
|
/** True once the client is gone or the app closed the socket. */
|
|
35
39
|
readonly closed: boolean;
|
|
36
40
|
/** The next frame from the client, or null once it is gone. */
|
package/dist/stream.d.ts
CHANGED
|
@@ -113,6 +113,11 @@ export declare function binaryField(objectSchema: any): string | null;
|
|
|
113
113
|
*
|
|
114
114
|
* A frame that does not fit the input schema is answered with
|
|
115
115
|
* `{"error": {"field": ..., "message": ...}}` and skipped; the stream goes on.
|
|
116
|
+
*
|
|
117
|
+
* The function may instead be an async generator (`async *talk(inputData, socket)`):
|
|
118
|
+
* each yield is a cumulative snapshot of the task's output, sent as a task update
|
|
119
|
+
* rather than over the socket, and the last yield (after the loop, once the client
|
|
120
|
+
* closes) is the result.
|
|
116
121
|
*/
|
|
117
122
|
export declare class Live {
|
|
118
123
|
readonly socket: Socket;
|
package/dist/stream.js
CHANGED
|
@@ -199,6 +199,11 @@ function firstIssue(result) {
|
|
|
199
199
|
*
|
|
200
200
|
* A frame that does not fit the input schema is answered with
|
|
201
201
|
* `{"error": {"field": ..., "message": ...}}` and skipped; the stream goes on.
|
|
202
|
+
*
|
|
203
|
+
* The function may instead be an async generator (`async *talk(inputData, socket)`):
|
|
204
|
+
* each yield is a cumulative snapshot of the task's output, sent as a task update
|
|
205
|
+
* rather than over the socket, and the last yield (after the loop, once the client
|
|
206
|
+
* closes) is the result.
|
|
202
207
|
*/
|
|
203
208
|
export class Live {
|
|
204
209
|
socket;
|
package/package.json
CHANGED