@jigs.design/server 0.2.0 → 0.3.1

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/dist/types.d.ts CHANGED
@@ -2,8 +2,8 @@ import { Advice, DesignCheckCode, Placement } from "./design.js";
2
2
  import { ConfirmationStore } from "./confirm.js";
3
3
  import { EditFailureReason } from "@jigs.design/compiler";
4
4
  import { PromptMode } from "@jigs.design/prompt";
5
- import { ActionDefinition, CallPolicy, Catalog, Host, InputOf, MutationDefinition, OutputOf, ProcedureDefinition, Procedures } from "@jigs.design/host";
6
- import { ChatBlock, ChatBlockStatus, Diagnostic, Invalidation, JigsErrorData, Message, Program, SessionEvent } from "@jigs.design/protocol";
5
+ import { ActionDefinition, CallPolicy, Catalog, DataDefinition, Host, InputOf, JigDefinition, MutationDefinition, OutputOf, PageName, ProcedureDefinition, Procedures, QueryDefinition } from "@jigs.design/host";
6
+ import { ChatBlock, ChatBlockStatus, Diagnostic, Invalidation, JigsErrorData, JsonValue, Message, PageInvalidation, PageProps, PageRef, Program, SessionEvent } from "@jigs.design/protocol";
7
7
  import { z } from "zod";
8
8
  //#region src/types.d.ts
9
9
  /**
@@ -25,7 +25,11 @@ export type ModelRequestKind = "chat-repair" | "repair" | "session";
25
25
  export interface ModelRequest<TContext, TOptions> {
26
26
  ctx: TContext;
27
27
  kind: ModelRequestKind;
28
- /** The conversation, with the server's repair turns added. */
28
+ /**
29
+ * The conversation, with the server's repair turns added. In a session
30
+ * about a document, the last user message carries the current UI before
31
+ * the ask (`withDocument`), on every call.
32
+ */
29
33
  messages: ReadonlyArray<Message>;
30
34
  options: TOptions;
31
35
  signal: AbortSignal;
@@ -51,9 +55,10 @@ export interface TurnEdits {
51
55
  blocks: number;
52
56
  /**
53
57
  * Why the first edit that failed did: its SEARCH text is not in the
54
- * source (`"missing"`) or is there more than once (`"ambiguous"`), the
55
- * block is broken (`"malformed"`), or the edit left a tag unclosed or
56
- * closed one twice (`"broken"`).
58
+ * source (`"missing"`) or is there more than once (`"ambiguous"`), an
59
+ * earlier edit of the reply changes some of the same lines
60
+ * (`"overlapping"`), the block is broken (`"malformed"`), or the edit
61
+ * left a tag unclosed or closed one twice (`"broken"`).
57
62
  */
58
63
  reason?: EditFailureReason;
59
64
  }
@@ -110,6 +115,12 @@ export interface Turn<TContext, TOptions> {
110
115
  ctx: TContext;
111
116
  /** The diagnostics sent with `done`; empty when the turn did not finish. */
112
117
  diagnostics: ReadonlyArray<Diagnostic>;
118
+ /**
119
+ * The source the turn started from, as the request carried it or the
120
+ * page it named resolved to; null when the turn wrote a new UI. A chat
121
+ * repair's is the block it repairs.
122
+ */
123
+ document: string | null;
113
124
  /**
114
125
  * Milliseconds from the start of the turn to its first `program` event;
115
126
  * null when it sent none. A chat repair sends its block once, when it is
@@ -118,6 +129,7 @@ export interface Turn<TContext, TOptions> {
118
129
  firstProgramMs: number | null;
119
130
  /** A session turn, or the repair of a chat block, which is a turn of its own. */
120
131
  kind: "chat-repair" | "session";
132
+ /** The conversation as the request carried it, without the document. */
121
133
  messages: ReadonlyArray<Message>;
122
134
  options: TOptions;
123
135
  /**
@@ -147,9 +159,10 @@ type DeepReadonly<T> = T extends object ? { readonly [K in keyof T]: DeepReadonl
147
159
  type RunOutput<P extends ProcedureDefinition> = DeepReadonly<OutputOf<P>>;
148
160
  /**
149
161
  * One function per query and mutation. Actions have none: the page handles
150
- * them, and the server only checks their input.
162
+ * them, and the server only checks their input. Provided data has none
163
+ * either: the page supplies it.
151
164
  */
152
- export type RunFunctions<TProcedures extends Procedures, TContext> = { readonly [K in keyof TProcedures as TProcedures[K] extends ActionDefinition ? never : K]: (input: InputOf<TProcedures[K]>, call: CallContext<TContext>) => RunOutput<TProcedures[K]> | Promise<RunOutput<TProcedures[K]>>; };
165
+ export type RunFunctions<TProcedures extends Procedures, TContext> = { readonly [K in keyof TProcedures as TProcedures[K] extends ActionDefinition | DataDefinition ? never : K]: (input: InputOf<TProcedures[K]>, call: CallContext<TContext>) => RunOutput<TProcedures[K]> | Promise<RunOutput<TProcedures[K]>>; };
153
166
  export type ContextFunction<TContext> = (request: Request) => TContext | Promise<TContext>;
154
167
  /** The names of a host's mutations. */
155
168
  type MutationName<TProcedures extends Procedures> = { [K in keyof TProcedures & string]: TProcedures[K] extends MutationDefinition ? K : never; }[keyof TProcedures & string];
@@ -168,39 +181,142 @@ export type PolicyCall<TProcedures extends Procedures, TContext> = { [K in Mutat
168
181
  * as "requires-action" when the server asks the page, and once as
169
182
  * "confirmed" when it ran with the user's approval.
170
183
  */
171
- export type CallReportOutcome = "confirmed" | "denied" | "error" | "requires-action" | "success";
172
- /** One call to a query, mutation or action, as `onCall` receives it. */
184
+ export type CallReportOutcome = "aborted" | "confirmed" | "denied" | "error" | "requires-action" | "success";
185
+ /**
186
+ * One call to a query, mutation or action, or one page request, as
187
+ * `onCall` receives it.
188
+ */
173
189
  export interface CallReport<TContext> {
174
190
  ctx: TContext;
175
191
  /** From the call's arrival to its outcome, in milliseconds. */
176
192
  durationMs: number;
177
- /** What the browser received, when `outcome` is not "success". */
193
+ /** What the browser received, when `outcome` is "denied" or "error". */
178
194
  error?: JigsErrorData;
179
195
  /**
180
196
  * The parsed input, or the input as the browser sent it when the call
181
197
  * failed before its input parsed.
182
198
  */
183
199
  input: unknown;
184
- /** The kind the call declared. */
185
- kind: ProcedureDefinition["kind"];
200
+ /**
201
+ * The kind the call declared, or "page" for a page request on the wire,
202
+ * whose `input` is its props.
203
+ */
204
+ kind: ProcedureDefinition["kind"] | "page";
186
205
  name: string;
187
206
  /**
188
- * "denied" when a policy or a bad confirmation refused the call, "error"
189
- * for any other failure, including one `run` threw.
207
+ * "denied" when a policy or a bad confirmation refused the call, or
208
+ * `pagePolicy` a page; "error" for any other failure, including one
209
+ * `run` threw and a page no list has; "aborted" when `run` rejected
210
+ * after the browser abandoned the call (its `signal` aborted), which
211
+ * does not reach `onError`.
190
212
  */
191
213
  outcome: CallReportOutcome;
192
214
  }
215
+ /** The names of a host's queries. */
216
+ type QueryName<TProcedures extends Procedures> = { [K in keyof TProcedures & string]: TProcedures[K] extends QueryDefinition ? K : never; }[keyof TProcedures & string];
217
+ /**
218
+ * One query a push event refreshes, by name. `input` is typed against the
219
+ * query's input schema, as a mutation's scopes are, and the views' queries
220
+ * whose input contains it refetch; without it, every input does. It is a
221
+ * match key that every client the event reaches can read, never a record.
222
+ */
223
+ export type PushInvalidation<TProcedures extends Procedures> = { [K in QueryName<TProcedures>]: {
224
+ input?: TProcedures[K]["input"] extends z.ZodType ? z.input<TProcedures[K]["input"]> : never;
225
+ name: K;
226
+ }; }[QueryName<TProcedures>];
227
+ /** Queries for one client's push stream to refetch. */
228
+ export interface PushInvalidate<TProcedures extends Procedures> {
229
+ invalidate: ReadonlyArray<PushInvalidation<TProcedures>>;
230
+ type: "invalidate";
231
+ }
232
+ /**
233
+ * One page a push event refreshes: a page of the host by its name, or, with
234
+ * `saved: true`, any name `resolvePage` may answer. Without `props`, every
235
+ * props of the page refetches; with them, the pages whose props contain
236
+ * them. `saved` stays on the server: the client sees `{ name, props }`.
237
+ */
238
+ export type PushPage<TPageName extends string> = PageInvalidation<TPageName> | (PageInvalidation & {
239
+ saved: true;
240
+ });
241
+ /** Pages for one client's push stream to refetch, by name. */
242
+ export interface PushInvalidatePages<TPageName extends string> {
243
+ pages: ReadonlyArray<PushPage<TPageName>>;
244
+ type: "invalidate-pages";
245
+ }
246
+ /** What `send` writes to one client's push stream. */
247
+ export type PushSend<TProcedures extends Procedures, TPageName extends string> = PushInvalidate<TProcedures> | PushInvalidatePages<TPageName>;
248
+ /** One client's push stream, as the `events` option receives it. */
249
+ export interface EventSubscriber<TProcedures extends Procedures, TContext, TPageName extends string = string> {
250
+ /**
251
+ * Writes a `close` event and ends the stream, for a platform that caps how
252
+ * long a response may last: the client reconnects and, having been told,
253
+ * refetches nothing.
254
+ */
255
+ close: () => void;
256
+ /** What `context` returned for the request that opened the stream. */
257
+ ctx: TContext;
258
+ /**
259
+ * Writes one event. `id` is yours (a log offset, a stream id), at most
260
+ * 200 printable characters; it comes back as `since` when the client
261
+ * reconnects. Give ids only if you replay from `since`: a client that
262
+ * resumed refetches nothing on its own. A send after the stream ended is
263
+ * ignored.
264
+ */
265
+ send: (event: PushSend<TProcedures, TPageName>, options?: {
266
+ id?: string;
267
+ }) => void;
268
+ /** Aborts when the client leaves or the stream ends. */
269
+ signal: AbortSignal;
270
+ /**
271
+ * The `Last-Event-ID` the client reconnected with, or null: none, or one
272
+ * longer than 200 characters or with a character that is not printable.
273
+ */
274
+ since: string | null;
275
+ }
276
+ /**
277
+ * The host's push fan-out: subscribe `send` to your own source of changes
278
+ * (an in-process emitter, Postgres `LISTEN`, Redis) and return the
279
+ * unsubscribe, which runs once when the stream ends. Filter by `ctx`: every
280
+ * event you send reaches that client.
281
+ */
282
+ export type EventsFunction<TProcedures extends Procedures, TContext, TPageName extends string = string> = (subscriber: EventSubscriber<TProcedures, TContext, TPageName>) => (() => void) | Promise<() => void>;
193
283
  export interface ErrorInfo {
194
- /** The procedure, for a call. */
284
+ /** The procedure, for a call; the page, for a page. */
195
285
  name?: string;
196
- /** The request it came from: a call, a session turn, or a chat turn with its repairs. */
197
- type: "call" | "chat" | "session";
286
+ /**
287
+ * The request it came from: a call, a session turn, a chat turn with its
288
+ * repairs, the push stream, or a page (`jigs.page` or its wire request).
289
+ */
290
+ type: "call" | "chat" | "events" | "page" | "session";
291
+ }
292
+ /** A page `resolvePage` found: the source as the app stored it. */
293
+ export interface SavedPage {
294
+ /** The source, with its pragma line when the app kept it. */
295
+ source: string;
198
296
  }
297
+ /**
298
+ * Finds a page the host does not ship, by name, for the caller `ctx` names.
299
+ * `null` is "no such page for this caller". Runs on every request for the
300
+ * name; cache in it if the store is slow. Throw a `JigsError` to refuse.
301
+ */
302
+ export type PageResolver<TContext> = (name: string, call: {
303
+ ctx: TContext;
304
+ signal?: AbortSignal;
305
+ }) => Promise<SavedPage | null> | SavedPage | null;
306
+ /**
307
+ * Whether a shipped page may be served on the wire to the caller `ctx`
308
+ * names. Saved pages are scoped by `resolvePage` instead.
309
+ */
310
+ export type PagePolicy<TContext, TPageName extends string = string> = (page: {
311
+ ctx: TContext;
312
+ name: TPageName;
313
+ props: PageProps;
314
+ }) => "allow" | "deny";
199
315
  /**
200
316
  * `TContext` is inferred from `context`; `run` only reads it (`NoInfer`), so
201
317
  * a mistake in one `run` function is reported at that function.
202
318
  */
203
- export interface JigsServerOptions<TProcedures extends Procedures, TCatalog extends Catalog, TContext, TOptions, TSpecName extends string = never> {
319
+ export interface JigsServerOptions<TProcedures extends Procedures, TCatalog extends Catalog, TContext, TJigs extends ReadonlyArray<JigDefinition> = ReadonlyArray<JigDefinition>> {
204
320
  /** Origins whose views may call this server and embed its frame. */
205
321
  allowedOrigins?: ReadonlyArray<string>;
206
322
  /**
@@ -221,12 +337,12 @@ export interface JigsServerOptions<TProcedures extends Procedures, TCatalog exte
221
337
  */
222
338
  context: ContextFunction<TContext>;
223
339
  /**
224
- * Design checks whose advice gets a repair round, as errors do: at most
225
- * one per turn or chat block, within `maxRepairs`. Default none, since a
226
- * repair round is a full model call.
340
+ * Opens the push stream: a `GET` to the handler with `Accept:
341
+ * text/event-stream`, after `context`. Without it, that `GET` answers
342
+ * `204` and the client stops asking. See `EventsFunction`.
227
343
  */
228
- designRepair?: ReadonlyArray<DesignCheckCode>;
229
- host: Host<TProcedures, TCatalog>;
344
+ events?: EventsFunction<NoInfer<TProcedures>, NoInfer<TContext>, NoInfer<PageName<TJigs>>>;
345
+ host: Host<TProcedures, TCatalog, TJigs>;
230
346
  /**
231
347
  * The app's own instructions to the model, such as its persona, today's
232
348
  * date or its safety rules. Every prompt carries them: document-mode
@@ -235,23 +351,26 @@ export interface JigsServerOptions<TProcedures extends Procedures, TCatalog exte
235
351
  instructions?: string;
236
352
  /** The largest request body accepted, in bytes. Default 1,000,000. */
237
353
  maxBodyBytes?: number;
238
- /** Repair rounds after a turn that ends with errors. Default 1. */
239
- maxRepairs?: number;
240
- model?: ModelFunction<TContext, TOptions>;
241
354
  /** The clock confirmation tokens expire by, in ms since the epoch. Default `Date.now`. */
242
355
  now?: () => number;
243
356
  /**
244
357
  * Runs after every call to a query, mutation or action, including ones
245
- * refused before `run`. The response does not wait for it, and a value it
358
+ * refused before `run`, and after every page request on the wire, with
359
+ * `kind: "page"`, so both can be audited and rate-limited by `ctx`. The response does not wait for it, and a value it
246
360
  * throws or rejects with goes to `onError`.
247
361
  */
248
362
  onCall?: (call: CallReport<NoInfer<TContext>>) => Promise<void> | void;
249
363
  onError?: (cause: unknown, info: ErrorInfo) => void;
250
- onTurn?: (turn: Turn<TContext, TOptions>) => Promise<void> | void;
251
- /** Schema for the app options sent with each turn. */
252
- options?: z.ZodType<TOptions>;
253
364
  /** This server's origin, when the request URL does not show it (behind a proxy). */
254
365
  origin?: string;
366
+ /**
367
+ * Decides whether a shipped page is served on the wire (`POST { type:
368
+ * "page" }`) to this `ctx`, after `context` and before any compile.
369
+ * "deny" answers `NOT_FOUND`, as for a page no list has. Default: every
370
+ * page to everyone `context` admits. A value it throws, or anything but
371
+ * "allow" or "deny", goes to `onError` and the page is denied.
372
+ */
373
+ pagePolicy?: PagePolicy<NoInfer<TContext>, PageName<TJigs>>;
255
374
  /**
256
375
  * Decides a mutation call's policy from its `ctx` and parsed input, after
257
376
  * the input parses and before `run`; `undefined` keeps the host's. A
@@ -259,13 +378,12 @@ export interface JigsServerOptions<TProcedures extends Procedures, TCatalog exte
259
378
  */
260
379
  policy?: (call: PolicyCall<NoInfer<TProcedures>, NoInfer<TContext>>) => CallPolicy | undefined;
261
380
  /**
262
- * While a reply streams, the least time between two program updates, in
263
- * ms. Each update carries the whole program, so a long reply that changed
264
- * it at every line sent it hundreds of times. The first program goes out
265
- * at once, a newer one replaces one still waiting, and the reply's last
266
- * program always goes out. Default 250; 0 sends every change.
381
+ * Answers `page(name, props, { ctx })` and the wire's page requests for a
382
+ * name the host does not have, such as a page a user saved. A name on
383
+ * the host never reaches it. Saved pages compile leniently unless the
384
+ * call says `strict`. See `PageResolver`.
267
385
  */
268
- programIntervalMs?: number;
386
+ resolvePage?: PageResolver<NoInfer<TContext>>;
269
387
  run: RunFunctions<NoInfer<TProcedures>, NoInfer<TContext>>;
270
388
  /**
271
389
  * Signs confirmation tokens, at least 32 characters; keep it out of the
@@ -273,11 +391,33 @@ export interface JigsServerOptions<TProcedures extends Procedures, TCatalog exte
273
391
  * "confirm": the server refuses to start without it.
274
392
  */
275
393
  secret?: string;
394
+ }
395
+ /**
396
+ * What `withModel` takes: everything that exists only because a model does.
397
+ * `TContext` is already the core's, so `ctx` is typed inside `model` and
398
+ * `onTurn` with no inference of its own.
399
+ */
400
+ export interface ModelOptions<TContext, TOptions> {
276
401
  /**
277
- * Stored specs by name, compiled once at startup. A diagnostic worse than
278
- * info stops the server, as a bad recipe does. Read one with `spec(name)`.
402
+ * Design checks whose advice gets a repair round, as errors do: at most
403
+ * one per turn or chat block, within `maxRepairs`. Default none, since a
404
+ * repair round is a full model call.
279
405
  */
280
- specs?: Readonly<Record<TSpecName, string>>;
406
+ designRepair?: ReadonlyArray<DesignCheckCode>;
407
+ /** Repair rounds after a turn that ends with errors. Default 1. */
408
+ maxRepairs?: number;
409
+ model: ModelFunction<TContext, TOptions>;
410
+ onTurn?: (turn: Turn<TContext, TOptions>) => Promise<void> | void;
411
+ /** Schema for the app options sent with each turn. */
412
+ options?: z.ZodType<TOptions>;
413
+ /**
414
+ * While a reply streams, the least time between two program updates, in
415
+ * ms. Each update carries the whole program, so a long reply that changed
416
+ * it at every line sent it hundreds of times. The first program goes out
417
+ * at once, a newer one replaces one still waiting, and the reply's last
418
+ * program always goes out. Default 250; 0 sends every change.
419
+ */
420
+ programIntervalMs?: number;
281
421
  }
282
422
  export interface CompileOptions {
283
423
  /**
@@ -328,14 +468,20 @@ export interface CallRequest {
328
468
  name: string;
329
469
  }
330
470
  export interface SessionRequest {
331
- /** Plain JSON from the browser; parsed with the messages schema. */
471
+ /**
472
+ * Plain JSON from the browser: `{ source }`, or `{ name, props? }` for a
473
+ * page the server serves, opened as a document. Absent for a new UI.
474
+ */
475
+ document?: unknown;
476
+ /** Plain JSON from the browser; parsed with the session schema. */
332
477
  messages: unknown;
333
478
  /** Plain JSON from the browser; parsed with the `options` schema. */
334
479
  options?: unknown;
335
480
  }
336
481
  /** An earlier block of the conversation, as the chat stored it. */
337
482
  export interface ChatBlockSource {
338
- id: string;
483
+ /** The block's name, the fence id. */
484
+ name: string;
339
485
  /** Its checked source; with or without the pragma line. */
340
486
  source: string;
341
487
  }
@@ -359,16 +505,19 @@ export interface ChatBlockResult {
359
505
  /** Set when the block was SEARCH/REPLACE edits to an earlier block. */
360
506
  edits?: TurnEdits;
361
507
  /**
362
- * Set when the block is done without a program of its own: an edit to an
363
- * id the conversation does not have (`NOT_FOUND`), a block past the
508
+ * Set when the block is done without a program of its own: an edit to a
509
+ * name the conversation does not have (`NOT_FOUND`), a block past the
364
510
  * reply's limit (`RATE_LIMITED`), or an edit or repair that failed
365
511
  * (`INTERNAL`). The same error the block carries.
366
512
  */
367
513
  error?: JigsErrorData;
368
514
  /** False for Jigs the model wrote without a fence. */
369
515
  fenced: boolean;
370
- id: string;
371
516
  key: string;
517
+ /** The block's name, the fence id. */
518
+ name: string;
519
+ /** Set when the block is one tag naming a page, as on the block. */
520
+ ref?: PageRef;
372
521
  /**
373
522
  * The rounds of the block's repair, the ones its "chat-repair" turn gives
374
523
  * `onTurn`; empty when it had none. The last one's diagnostics are the
@@ -437,15 +586,58 @@ export interface ChatTurn {
437
586
  /** The start of `system` that is the same on every turn, as on `ModelRequest`. */
438
587
  systemPrefix: string;
439
588
  }
440
- export interface JigsServer<TContext, TSpecName extends string = never> {
441
- call: (request: CallRequest, context: CallContext<TContext>) => Promise<CallOutcome>;
589
+ /** A page, compiled, and what the checks said about it. */
590
+ export interface ResolvedPage {
591
+ /** The design checks' advice, numbered like `diagnostics`. */
592
+ advice: ReadonlyArray<Advice>;
442
593
  /**
443
- * Starts a chat reply for `request`. It refuses the request as `handler`
444
- * would (an `Origin` that may not call this server, or a body that is not
445
- * JSON), then runs `context` and parses `blocks` and `options`. Each
446
- * failure rejects with a `JigsError`. See `ChatTurn`.
594
+ * Never an error or warning for a shipped page, or a saved one compiled
595
+ * with `strict`. A saved page is lenient by default: these say what
596
+ * broke, beside a program that still runs. Show a notice when one is.
447
597
  */
448
- chat: (request: Request, options?: ChatOptions) => Promise<ChatTurn>;
598
+ diagnostics: ReadonlyArray<Diagnostic>;
599
+ /** "shipped": a jig listed on the host; "saved": one `resolvePage` found. */
600
+ origin: "saved" | "shipped";
601
+ program: Program;
602
+ /**
603
+ * What was compiled: a document page's source, or a define page's
604
+ * one-tag use, such as `<orders status={"open"}/>`.
605
+ */
606
+ source: string;
607
+ }
608
+ /** How `page` finds a name the host may not ship: through `resolvePage`, for `ctx`. */
609
+ export interface PageCall<TContext> {
610
+ ctx: TContext;
611
+ signal?: AbortSignal;
612
+ /**
613
+ * Reject with a `JigsCompileError` when a saved page has an error or
614
+ * warning diagnostic, instead of resolving with them. Default false. A
615
+ * shipped page is always strict.
616
+ */
617
+ strict?: boolean;
618
+ }
619
+ /**
620
+ * A page by name: a shipped one with only a name and props, or any name
621
+ * with `{ ctx }`, which may ask `resolvePage`.
622
+ */
623
+ export interface PageFunction<TContext, TPageName extends string> {
624
+ /**
625
+ * A shipped page. Rejects with a `JigsError` whose code is `BAD_INPUT`
626
+ * when the props are not the define's, so a route that takes props from
627
+ * a URL answers 400.
628
+ */
629
+ (name: TPageName, props?: PageProps, call?: {
630
+ signal?: AbortSignal;
631
+ }): Promise<ResolvedPage>;
632
+ /**
633
+ * Any name: a shipped page wins and never asks `resolvePage`; otherwise
634
+ * `resolvePage` with `ctx`. Null when neither has it. A saved page takes
635
+ * no props (`BAD_INPUT`).
636
+ */
637
+ (name: string, props: PageProps | undefined, call: PageCall<TContext>): Promise<ResolvedPage | null>;
638
+ }
639
+ export interface JigsServer<TContext, TPageName extends string = never> {
640
+ call: (request: CallRequest, context: CallContext<TContext>) => Promise<CallOutcome>;
449
641
  /** Checks and compiles a source with no model. Strict unless told otherwise. */
450
642
  compile: (source: string, options?: CompileOptions) => Promise<{
451
643
  /** The design checks' advice, numbered like `diagnostics`. */
@@ -453,11 +645,38 @@ export interface JigsServer<TContext, TSpecName extends string = never> {
453
645
  diagnostics: ReadonlyArray<Diagnostic>;
454
646
  program: Program;
455
647
  }>;
648
+ /**
649
+ * `value` parsed with the schema of provided slot `name`, for a page
650
+ * that wants it checked before it passes it to `JigsView` as `data`.
651
+ * Throws `JigsError("BAD_INPUT")` when it does not match, or when `name`
652
+ * is not provided data on this host. The view does not check it: a value
653
+ * that does not match renders as the program reads it.
654
+ */
655
+ data: (name: string, value: JsonValue) => JsonValue;
656
+ /**
657
+ * The push stream for `request`, whose `ctx` the caller already has: a
658
+ * foreign `Origin` is `403`, a server without `events` answers `204`, and
659
+ * an `events` that throws is `500`. `handler` does this for a `GET` that
660
+ * accepts `text/event-stream`, after `context`.
661
+ */
662
+ events: (request: Request, context: CallContext<TContext>) => Promise<Response>;
456
663
  /** The sandbox frame document, with its own content security policy. */
457
664
  frame: (request?: Request) => Response;
665
+ /**
666
+ * Serves the frame, the push stream, calls, pages and session turns. Without `withModel`, a
667
+ * session turn that would ask the model answers `404 NOT_FOUND`; one
668
+ * that carries a document and no ask is still compiled and streamed.
669
+ */
458
670
  handler: (request: Request) => Promise<Response>;
459
671
  /** The largest request body `handler` accepts, in bytes. */
460
672
  maxBodyBytes: number;
673
+ /**
674
+ * A page, compiled: a jig with `page: true`, by its name, with the props
675
+ * its `<define>` declares, or with `{ ctx }`, a page `resolvePage` finds.
676
+ * Shipped pages that need no props compile at startup, and every shipped
677
+ * page compiles strictly. See `PageFunction`.
678
+ */
679
+ page: PageFunction<TContext, TPageName>;
461
680
  /**
462
681
  * The system prompt for this host. "document" (default) is what sessions
463
682
  * use: every reply is one UI. "chat" is Markdown with UIs in fenced
@@ -471,11 +690,29 @@ export interface JigsServer<TContext, TSpecName extends string = never> {
471
690
  * `@jigs.design/ai-sdk`).
472
691
  */
473
692
  promptPrefix: (mode?: PromptMode) => string;
474
- session: (body: SessionRequest, context: CallContext<TContext>) => AsyncIterable<SessionEvent>;
475
- /** A spec from `specs`, compiled at startup. */
476
- spec: (name: TSpecName) => Program;
477
693
  /** Changes whenever the compiler, the runtime or the host does. */
478
694
  version: string;
695
+ /**
696
+ * This server with a model: the same core, plus `session` and `chat`,
697
+ * and a handler that runs session turns that ask the model.
698
+ */
699
+ withModel: <TOptions = undefined>(options: ModelOptions<TContext, TOptions>) => JigsModelServer<TContext, TPageName>;
700
+ }
701
+ /** What `withModel` returns: the core, plus the turns that call the model. */
702
+ export interface JigsModelServer<TContext, TPageName extends string = never> extends Omit<JigsServer<TContext, TPageName>, "withModel"> {
703
+ /**
704
+ * Starts a chat reply for `request`. It refuses the request as `handler`
705
+ * would (an `Origin` that may not call this server, or a body that is not
706
+ * JSON), then runs `context` and parses `blocks` and `options`. Each
707
+ * failure rejects with a `JigsError`. See `ChatTurn`.
708
+ */
709
+ chat: (request: Request, options?: ChatOptions) => Promise<ChatTurn>;
710
+ /**
711
+ * One session turn: a document whose conversation does not end with an
712
+ * ask is compiled again with no model call; otherwise the model answers,
713
+ * editing the document when there is one.
714
+ */
715
+ session: (body: SessionRequest, context: CallContext<TContext>) => AsyncIterable<SessionEvent>;
479
716
  }
480
717
  //#endregion
481
718
  export type { EditFailureReason };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jigs.design/server",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "createJigsServer: checks and streams programs, runs procedures, and serves the sandbox frame.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,11 +29,11 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@jigs.design/compiler": "0.2.0",
33
- "@jigs.design/host": "0.2.0",
34
- "@jigs.design/prompt": "0.2.0",
35
- "@jigs.design/protocol": "0.2.0",
36
- "@jigs.design/runtime": "0.2.0"
32
+ "@jigs.design/host": "0.3.1",
33
+ "@jigs.design/compiler": "0.3.1",
34
+ "@jigs.design/protocol": "0.3.1",
35
+ "@jigs.design/runtime": "0.3.1",
36
+ "@jigs.design/prompt": "0.3.1"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "zod": "^4.0.0"