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