@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inferencesh/app",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "App framework for building inference.sh apps — File handling, output metadata, storage utilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",