@jigs.design/server 0.3.5 → 0.3.7
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/chat.js +1 -1
- package/dist/compile-cache.d.ts +2 -0
- package/dist/compile-cache.js +14 -6
- package/dist/compile.js +65 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +186 -18
- package/dist/index.d.ts +2 -2
- package/dist/model-server.js +50 -28
- package/dist/pragma.d.ts +1 -0
- package/dist/scope.d.ts +7 -0
- package/dist/scope.js +154 -0
- package/dist/session.js +7 -5
- package/dist/types.d.ts +139 -5
- package/package.json +6 -6
package/dist/chat.js
CHANGED
|
@@ -624,7 +624,7 @@ async function* repairBlock(deps, block) {
|
|
|
624
624
|
source: event.source,
|
|
625
625
|
status: "done"
|
|
626
626
|
};
|
|
627
|
-
const compiled = deps.session.
|
|
627
|
+
const compiled = deps.session.compile(body);
|
|
628
628
|
const { ref } = compiled;
|
|
629
629
|
if (ref !== null) repaired.ref = ref;
|
|
630
630
|
if (compiled.toolKeys !== null) repaired.toolKeys = compiled.toolKeys;
|
package/dist/compile-cache.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
/** The default bound: 32 MB of programs, as JSON. */
|
|
3
3
|
const DEFAULT_MAX_BYTES = 33554432;
|
|
4
4
|
/**
|
|
5
|
-
* A cache bounded by the
|
|
6
|
-
* first. A
|
|
5
|
+
* A cache bounded by the sum of `sizeOf` over its values, and by
|
|
6
|
+
* `maxEntries`, least recently used out first. A value larger than the
|
|
7
|
+
* whole bound is not kept.
|
|
7
8
|
*/
|
|
8
|
-
function
|
|
9
|
+
function boundedCache(maxBytes, sizeOf, maxEntries = Number.POSITIVE_INFINITY) {
|
|
9
10
|
const entries = /* @__PURE__ */ new Map();
|
|
10
11
|
let bytes = 0;
|
|
11
12
|
const drop = (key) => {
|
|
@@ -28,7 +29,7 @@ function memoryCompileCache(maxBytes = DEFAULT_MAX_BYTES) {
|
|
|
28
29
|
},
|
|
29
30
|
set: (key, value) => {
|
|
30
31
|
drop(key);
|
|
31
|
-
const size =
|
|
32
|
+
const size = sizeOf(value);
|
|
32
33
|
if (size > maxBytes) return;
|
|
33
34
|
entries.set(key, {
|
|
34
35
|
size,
|
|
@@ -36,7 +37,7 @@ function memoryCompileCache(maxBytes = DEFAULT_MAX_BYTES) {
|
|
|
36
37
|
});
|
|
37
38
|
bytes += size;
|
|
38
39
|
for (const oldest of entries.keys()) {
|
|
39
|
-
if (bytes <= maxBytes) break;
|
|
40
|
+
if (bytes <= maxBytes && entries.size <= maxEntries) break;
|
|
40
41
|
drop(oldest);
|
|
41
42
|
}
|
|
42
43
|
},
|
|
@@ -46,6 +47,13 @@ function memoryCompileCache(maxBytes = DEFAULT_MAX_BYTES) {
|
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
50
|
+
* A cache bounded by the size of its programs, least recently used out
|
|
51
|
+
* first. A program larger than the whole bound is not kept.
|
|
52
|
+
*/
|
|
53
|
+
function memoryCompileCache(maxBytes = DEFAULT_MAX_BYTES) {
|
|
54
|
+
return boundedCache(maxBytes, (value) => JSON.stringify(value.program).length);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
49
57
|
* The one cache every server in this process shares. Keys start with a
|
|
50
58
|
* hash of what the server compiles against, so two hosts never read each
|
|
51
59
|
* other's entries.
|
|
@@ -57,4 +65,4 @@ async function sha256(text) {
|
|
|
57
65
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
58
66
|
}
|
|
59
67
|
//#endregion
|
|
60
|
-
export { memoryCompileCache, processCompileCache, sha256 };
|
|
68
|
+
export { boundedCache, memoryCompileCache, processCompileCache, sha256 };
|
package/dist/compile.js
CHANGED
|
@@ -3,7 +3,7 @@ import { JigsCompileError, JigsError, fatalDiagnostics } from "./errors.js";
|
|
|
3
3
|
import { inputDiagnostics } from "./inputs.js";
|
|
4
4
|
import { outputDiagnostics } from "./outputs.js";
|
|
5
5
|
import { splitPragma, versionDiagnostics } from "./pragma.js";
|
|
6
|
-
import { compileJigs, compileLibrary, nearest, spanIn } from "@jigs.design/compiler";
|
|
6
|
+
import { compileJigs, compileLibrary, extendLibrary, nearest, parseJigs, spanIn } from "@jigs.design/compiler";
|
|
7
7
|
import { TOOL_RESULTS } from "@jigs.design/prompt";
|
|
8
8
|
import { componentSchema } from "@jigs.design/host";
|
|
9
9
|
import { PROGRAM_FORMAT } from "@jigs.design/protocol";
|
|
@@ -198,6 +198,40 @@ function refOf(tree, pages) {
|
|
|
198
198
|
props: sortedProps(props)
|
|
199
199
|
};
|
|
200
200
|
}
|
|
201
|
+
/** The element names `source` writes, whether or not the library has them. */
|
|
202
|
+
function writtenNames(source) {
|
|
203
|
+
const names = /* @__PURE__ */ new Set();
|
|
204
|
+
const visit = (nodes) => {
|
|
205
|
+
for (const node of nodes) if (node.type === "element") {
|
|
206
|
+
names.add(node.name);
|
|
207
|
+
visit(node.children);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
visit(parseJigs(source).children);
|
|
211
|
+
return names;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* `defines` ordered so each comes after the others it writes, in list
|
|
215
|
+
* order where the order is free; the members of a cycle, and what waits on
|
|
216
|
+
* them, last, in list order. Read from the parse rather than a compile: a
|
|
217
|
+
* compile drops an element its library lacks, so a define compiled before
|
|
218
|
+
* the one it writes would not show it.
|
|
219
|
+
*/
|
|
220
|
+
function dependencyOrder(defines) {
|
|
221
|
+
const names = new Set(defines.map((entry) => entry.name));
|
|
222
|
+
const needs = new Map(defines.map((entry) => [entry.name, [...writtenNames(entry.body)].filter((name) => name !== entry.name && names.has(name))]));
|
|
223
|
+
const ordered = [];
|
|
224
|
+
const placed = /* @__PURE__ */ new Set();
|
|
225
|
+
for (let progressed = true; progressed;) {
|
|
226
|
+
progressed = false;
|
|
227
|
+
for (const entry of defines) if (!placed.has(entry.name) && (needs.get(entry.name) ?? []).every((name) => placed.has(name))) {
|
|
228
|
+
ordered.push(entry);
|
|
229
|
+
placed.add(entry.name);
|
|
230
|
+
progressed = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return [...ordered, ...defines.filter((entry) => !placed.has(entry.name))];
|
|
234
|
+
}
|
|
201
235
|
/**
|
|
202
236
|
* The host, ready to check sources: its catalog in the compiler's form and
|
|
203
237
|
* its define jigs compiled into two libraries, one of the tags the model
|
|
@@ -298,6 +332,35 @@ function createChecker(host) {
|
|
|
298
332
|
};
|
|
299
333
|
};
|
|
300
334
|
const compile = (body) => compileWith(body, tagLibrary, tagFacts);
|
|
335
|
+
const extend = (saved) => {
|
|
336
|
+
const extended = extendLibrary(tagLibrary, dependencyOrder(saved).map((entry) => ({
|
|
337
|
+
name: entry.name,
|
|
338
|
+
source: entry.body
|
|
339
|
+
})), catalog);
|
|
340
|
+
const facts = new Map(tagFacts);
|
|
341
|
+
const diagnostics = /* @__PURE__ */ new Map();
|
|
342
|
+
const kept = /* @__PURE__ */ new Set();
|
|
343
|
+
for (const entry of saved) {
|
|
344
|
+
const found = [...extended.diagnostics.get(entry.name) ?? []];
|
|
345
|
+
const define = extended.library.get(entry.name);
|
|
346
|
+
if (define !== void 0 && !tagLibrary.has(entry.name)) {
|
|
347
|
+
const uses = [...define.procedures];
|
|
348
|
+
found.push(...procedureDiagnostics(host, entry.body, uses), ...inputDiagnostics(host, entry.body));
|
|
349
|
+
facts.set(entry.name, {
|
|
350
|
+
tags: define.tags,
|
|
351
|
+
uses
|
|
352
|
+
});
|
|
353
|
+
kept.add(entry.name);
|
|
354
|
+
}
|
|
355
|
+
diagnostics.set(entry.name, found.toSorted((a, b) => a.span.line - b.span.line));
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
compile: (body) => compileWith(body, extended.library, facts),
|
|
359
|
+
diagnostics,
|
|
360
|
+
kept,
|
|
361
|
+
tags: /* @__PURE__ */ new Set([...tags, ...kept])
|
|
362
|
+
};
|
|
363
|
+
};
|
|
301
364
|
const definePage = (name) => {
|
|
302
365
|
const define = fullLibrary.get(name);
|
|
303
366
|
if (define === void 0 || !pages.has(name)) throw new Error(`"${name}" is not a define page on this host`);
|
|
@@ -333,6 +396,7 @@ function createChecker(host) {
|
|
|
333
396
|
}
|
|
334
397
|
return {
|
|
335
398
|
compile,
|
|
399
|
+
extend,
|
|
336
400
|
needsProps: (name) => definePage(name).props.some((prop) => prop.required),
|
|
337
401
|
page: (name, props) => compileWith(pageBody(name, props), fullLibrary, allFacts),
|
|
338
402
|
pageBody,
|
package/dist/core.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import "./compile.js";
|
|
2
1
|
import { ContextFunction, JigsServer, JigsServerOptions, RunFunctions } from "./types.js";
|
|
3
2
|
import "./session.js";
|
|
4
3
|
import "./handler.js";
|
|
4
|
+
import "./scope.js";
|
|
5
5
|
import { Catalog, Host, JigDefinition, PageName, Procedures } from "@jigs.design/host";
|
|
6
6
|
import { JigsErrorData } from "@jigs.design/protocol";
|
|
7
7
|
//#region src/core.d.ts
|
package/dist/core.js
CHANGED
|
@@ -8,8 +8,10 @@ import { processCompileCache, sha256 } from "./compile-cache.js";
|
|
|
8
8
|
import { eventStream } from "./events.js";
|
|
9
9
|
import { invalidationsOf } from "./invalidation.js";
|
|
10
10
|
import { withToolResults } from "./tool-results.js";
|
|
11
|
+
import { checkedTag, listHashInput, processScopeCache, promptTag, savedTagListSchema, savedTagSchema, scanTag, scopeHashInput, tagDiagnostics, withTail } from "./scope.js";
|
|
11
12
|
import { createModelServer } from "./model-server.js";
|
|
12
|
-
import {
|
|
13
|
+
import { RESERVED_TAGS } from "@jigs.design/compiler";
|
|
14
|
+
import { savedTagsSection, systemPromptParts } from "@jigs.design/prompt";
|
|
13
15
|
import { isCallPolicy } from "@jigs.design/host";
|
|
14
16
|
import { PROGRAM_FORMAT } from "@jigs.design/protocol";
|
|
15
17
|
import { z } from "zod";
|
|
@@ -380,10 +382,19 @@ function createJigsServer(options) {
|
|
|
380
382
|
reportCall(report);
|
|
381
383
|
return outcome;
|
|
382
384
|
};
|
|
383
|
-
/**
|
|
384
|
-
const
|
|
385
|
+
/** What a request without saved tags compiles and prompts with: the host alone. */
|
|
386
|
+
const hostScope = {
|
|
387
|
+
compile: checker.compile,
|
|
388
|
+
hash: null,
|
|
389
|
+
rows: [],
|
|
390
|
+
saved: [],
|
|
391
|
+
tags: checker.tags,
|
|
392
|
+
tail: ""
|
|
393
|
+
};
|
|
394
|
+
/** A stored source: its pragma's versions are checked, then its body compiled in `scope`. */
|
|
395
|
+
const compileStored = (source, placement = "document", scope = hostScope) => {
|
|
385
396
|
const stored = splitPragma(source);
|
|
386
|
-
const compiled =
|
|
397
|
+
const compiled = scope.compile(stored.body);
|
|
387
398
|
const prefix = source.slice(0, source.length - stored.body.length);
|
|
388
399
|
const result = {
|
|
389
400
|
advice: belowPrefix(compiled.advise(placement), prefix),
|
|
@@ -395,19 +406,21 @@ function createJigsServer(options) {
|
|
|
395
406
|
};
|
|
396
407
|
/**
|
|
397
408
|
* `compile` through the process's cache, keyed by what this server
|
|
398
|
-
* compiles against, the
|
|
399
|
-
*
|
|
400
|
-
*
|
|
409
|
+
* compiles against, the scope's hash when the caller has saved tags, the
|
|
410
|
+
* placement (or "page", for a define page's body, which compiles against
|
|
411
|
+
* every define jig and never a scope) and the body's hash. Only final
|
|
412
|
+
* artifacts come here: a reply's partial sources stay on `checker`.
|
|
401
413
|
*/
|
|
402
|
-
const cached = async (placement, body, compile) => {
|
|
403
|
-
const
|
|
414
|
+
const cached = async (placement, body, compile, scope = hostScope) => {
|
|
415
|
+
const hash = await sha256(body);
|
|
416
|
+
const key = scope.hash === null ? `${await namespace}.${placement}.${hash}` : `${await namespace}.${scope.hash}.${placement}.${hash}`;
|
|
404
417
|
const hit = processCompileCache.get(key);
|
|
405
418
|
if (hit !== void 0) return hit;
|
|
406
419
|
const compiled = compile();
|
|
407
420
|
processCompileCache.set(key, compiled);
|
|
408
421
|
return compiled;
|
|
409
422
|
};
|
|
410
|
-
const compileCached = (source, placement = "document") => cached(placement, source, () => compileStored(source, placement));
|
|
423
|
+
const compileCached = (source, placement = "document", scope = hostScope) => cached(placement, source, () => compileStored(source, placement, scope), scope);
|
|
411
424
|
const jigsByName = new Map(host.jigs.map((entry) => [entry.name, entry]));
|
|
412
425
|
/** A define page's one-tag body compiled; it throws as `pageBody` does. */
|
|
413
426
|
const compileDefine = (name, props) => {
|
|
@@ -488,7 +501,8 @@ function createJigsServer(options) {
|
|
|
488
501
|
const savedPage = async (name, props, lookup) => {
|
|
489
502
|
const saved = await savedSource(name, props, lookup);
|
|
490
503
|
if (saved === null) return null;
|
|
491
|
-
const
|
|
504
|
+
const scope = await scopeFor(lookup);
|
|
505
|
+
const compiled = await compileCached(saved.source, "document", scope);
|
|
492
506
|
return {
|
|
493
507
|
...lookup.strict === true ? strictly(`Page "${name}"`, compiled) : compiled,
|
|
494
508
|
data: saved.data,
|
|
@@ -503,10 +517,12 @@ function createJigsServer(options) {
|
|
|
503
517
|
return entry === void 0 ? savedPage(name, props, lookup) : null;
|
|
504
518
|
};
|
|
505
519
|
const page = resolve;
|
|
506
|
-
|
|
507
|
-
|
|
520
|
+
/** `compile` in `scope`. Async so a strict failure is a rejection. */
|
|
521
|
+
const compileIn = async (scope, source, compileOptions) => {
|
|
522
|
+
const compiled = await compileCached(source, compileOptions?.placement, scope);
|
|
508
523
|
return compileOptions?.strict === false ? compiled : strictly("The source", compiled);
|
|
509
524
|
};
|
|
525
|
+
const compile = (source, compileOptions) => compileIn(hostScope, source, compileOptions);
|
|
510
526
|
/** Whether `pagePolicy` lets this `ctx` read a shipped page; it fails closed. */
|
|
511
527
|
const pageAllowed = (ctx, name, props) => {
|
|
512
528
|
if (options.pagePolicy === void 0) return true;
|
|
@@ -594,7 +610,8 @@ function createJigsServer(options) {
|
|
|
594
610
|
}
|
|
595
611
|
return checker.pageDocument(name, props);
|
|
596
612
|
};
|
|
597
|
-
|
|
613
|
+
/** The session's turn, its named page resolved for `ctx`. */
|
|
614
|
+
const sessionTurn = async (body, context) => {
|
|
598
615
|
const input = parseSessionInput(body);
|
|
599
616
|
if ("error" in input) return input;
|
|
600
617
|
const document = input.document;
|
|
@@ -621,6 +638,155 @@ function createJigsServer(options) {
|
|
|
621
638
|
messages: input.messages
|
|
622
639
|
};
|
|
623
640
|
};
|
|
641
|
+
const prepareSession = async (body, context) => {
|
|
642
|
+
const turn = await sessionTurn(body, context);
|
|
643
|
+
if ("error" in turn) return turn;
|
|
644
|
+
try {
|
|
645
|
+
return {
|
|
646
|
+
scope: await scopeFor(context),
|
|
647
|
+
turn
|
|
648
|
+
};
|
|
649
|
+
} catch (error) {
|
|
650
|
+
if (!isJigsError(error)) onError(error, { type: "tag" });
|
|
651
|
+
return { error: errorData(isJigsError(error) ? error : null) };
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* Names a saved tag may not take: the catalog's components, the
|
|
656
|
+
* language's own tags and every jig of the host, tag, page-only define
|
|
657
|
+
* or document page. The compiler keeps a define that shadows its library
|
|
658
|
+
* (`shadows_library` is info), so the scope refuses these itself.
|
|
659
|
+
*/
|
|
660
|
+
const reservedNames = /* @__PURE__ */ new Set([
|
|
661
|
+
...host.components.components.map((component) => component.name),
|
|
662
|
+
...RESERVED_TAGS,
|
|
663
|
+
...jigsByName.keys()
|
|
664
|
+
]);
|
|
665
|
+
/** A scanned tag's diagnostics, compiled among `others`, which it replaces one of by name. */
|
|
666
|
+
const checkAmong = (others, tag) => {
|
|
667
|
+
const extended = checker.extend([...others.filter((entry) => entry.name !== tag.name), tag].map((entry) => ({
|
|
668
|
+
body: entry.body,
|
|
669
|
+
name: entry.name
|
|
670
|
+
})));
|
|
671
|
+
return tagDiagnostics(tag, extended.diagnostics.get(tag.name) ?? [], host.components.version);
|
|
672
|
+
};
|
|
673
|
+
/** `checkTag` among a scope's rows: the host's, or a caller's. */
|
|
674
|
+
const checkTagIn = (rows, row, checkOptions) => {
|
|
675
|
+
const parsed = savedTagSchema.safeParse(row);
|
|
676
|
+
if (!parsed.success) throw new JigsError("BAD_INPUT", `A saved tag is { description, example?, source }: ${issuesText(parsed.error)}`);
|
|
677
|
+
const scanned = scanTag(parsed.data, reservedNames);
|
|
678
|
+
if ("message" in scanned) throw new JigsError("BAD_INPUT", scanned.message);
|
|
679
|
+
const diagnostics = checkAmong(rows, scanned);
|
|
680
|
+
if (checkOptions?.strict !== false) {
|
|
681
|
+
const fatal = fatalDiagnostics(diagnostics);
|
|
682
|
+
if (fatal.length > 0) throw new JigsCompileError(`Tag "${scanned.name}"`, fatal);
|
|
683
|
+
}
|
|
684
|
+
return checkedTag(scanned, diagnostics);
|
|
685
|
+
};
|
|
686
|
+
/**
|
|
687
|
+
* The scope for rows the resolver returned, each checked as `checkTag`
|
|
688
|
+
* would and dropped with `onError` when it fails: the host extended by
|
|
689
|
+
* the rest, in dependency order. The host's scope when none is kept.
|
|
690
|
+
*/
|
|
691
|
+
const buildScope = async (rows) => {
|
|
692
|
+
const kept = [];
|
|
693
|
+
const names = /* @__PURE__ */ new Set();
|
|
694
|
+
let total = 0;
|
|
695
|
+
for (const [index, row] of rows.entries()) {
|
|
696
|
+
if (row === null) {
|
|
697
|
+
onError(new JigsError("BAD_INPUT", "resolveTags returned an entry that is not { description, example?, source }"), { type: "tag" });
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const scanned = scanTag(row, reservedNames);
|
|
701
|
+
if ("message" in scanned) {
|
|
702
|
+
onError(new JigsError("BAD_INPUT", scanned.message), scanned.name === void 0 ? { type: "tag" } : {
|
|
703
|
+
name: scanned.name,
|
|
704
|
+
type: "tag"
|
|
705
|
+
});
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (names.has(scanned.name)) {
|
|
709
|
+
onError(new JigsError("BAD_INPUT", `resolveTags returned <${scanned.name}> twice; the first was kept`), {
|
|
710
|
+
name: scanned.name,
|
|
711
|
+
type: "tag"
|
|
712
|
+
});
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
if (kept.length >= 50 || total + scanned.source.length > 2e5) {
|
|
716
|
+
onError(/* @__PURE__ */ new Error(`resolveTags returned more than 50 tags or ${MAX_CONTENT} characters of source; <${scanned.name}> and the ${rows.length - index - 1} after it were dropped`), { type: "tag" });
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
kept.push(scanned);
|
|
720
|
+
names.add(scanned.name);
|
|
721
|
+
total += scanned.source.length;
|
|
722
|
+
}
|
|
723
|
+
if (kept.length === 0) return hostScope;
|
|
724
|
+
const extended = checker.extend(kept.map((tag) => ({
|
|
725
|
+
body: tag.body,
|
|
726
|
+
name: tag.name
|
|
727
|
+
})));
|
|
728
|
+
const saved = [];
|
|
729
|
+
for (const tag of kept) {
|
|
730
|
+
const diagnostics = tagDiagnostics(tag, extended.diagnostics.get(tag.name) ?? [], host.components.version);
|
|
731
|
+
if (extended.kept.has(tag.name)) saved.push(checkedTag(tag, diagnostics));
|
|
732
|
+
else onError(new JigsCompileError(`Tag "${tag.name}"`, fatalDiagnostics(diagnostics)), {
|
|
733
|
+
name: tag.name,
|
|
734
|
+
type: "tag"
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
if (saved.length === 0) return hostScope;
|
|
738
|
+
return {
|
|
739
|
+
compile: extended.compile,
|
|
740
|
+
hash: await sha256(scopeHashInput(kept)),
|
|
741
|
+
rows: kept,
|
|
742
|
+
saved,
|
|
743
|
+
tags: extended.tags,
|
|
744
|
+
tail: savedTagsSection(kept.filter((tag) => extended.kept.has(tag.name)).map((tag) => promptTag(tag)))
|
|
745
|
+
};
|
|
746
|
+
};
|
|
747
|
+
/**
|
|
748
|
+
* The scope for one request, after `context`: the host's without
|
|
749
|
+
* `resolveTags` or when it returns none, else the host extended by the
|
|
750
|
+
* caller's tags, from the process's scope cache when the same list was
|
|
751
|
+
* built before. A `JigsError` the resolver throws passes through; any
|
|
752
|
+
* other throw, or a value that is not an array, is `INTERNAL`.
|
|
753
|
+
*/
|
|
754
|
+
const scopeFor = async (lookup) => {
|
|
755
|
+
const resolveTags = options.resolveTags;
|
|
756
|
+
if (resolveTags === void 0) return hostScope;
|
|
757
|
+
let listed;
|
|
758
|
+
try {
|
|
759
|
+
listed = await resolveTags(lookup.signal === void 0 ? { ctx: lookup.ctx } : {
|
|
760
|
+
ctx: lookup.ctx,
|
|
761
|
+
signal: lookup.signal
|
|
762
|
+
});
|
|
763
|
+
} catch (error) {
|
|
764
|
+
if (isJigsError(error)) throw error;
|
|
765
|
+
onError(error, { type: "tag" });
|
|
766
|
+
throw new JigsError("INTERNAL", INTERNAL_MESSAGE);
|
|
767
|
+
}
|
|
768
|
+
const list = savedTagListSchema.safeParse(listed);
|
|
769
|
+
if (!list.success) {
|
|
770
|
+
onError(/* @__PURE__ */ new Error(`resolveTags returned ${String(listed)}; return an array of { description, example?, source }`), { type: "tag" });
|
|
771
|
+
throw new JigsError("INTERNAL", INTERNAL_MESSAGE);
|
|
772
|
+
}
|
|
773
|
+
const rows = list.data;
|
|
774
|
+
if (rows.length === 0) return hostScope;
|
|
775
|
+
const key = `${await namespace}.${await sha256(listHashInput(rows))}`;
|
|
776
|
+
const hit = processScopeCache.get(key);
|
|
777
|
+
if (hit !== void 0) return hit;
|
|
778
|
+
const scope = await buildScope(rows);
|
|
779
|
+
processScopeCache.set(key, scope);
|
|
780
|
+
return scope;
|
|
781
|
+
};
|
|
782
|
+
/** What `forUser` hands out for a scope. */
|
|
783
|
+
const userScope = (scope) => ({
|
|
784
|
+
checkTag: async (row, checkOptions) => checkTagIn(scope.rows, row, checkOptions),
|
|
785
|
+
compile: async (source, compileOptions) => compileIn(scope, source, compileOptions),
|
|
786
|
+
prompt: (mode) => withTail(mode === "chat" ? chatSystem : system, scope.tail),
|
|
787
|
+
promptPrefix: (mode) => mode === "chat" ? chatParts.stable : documentParts.stable,
|
|
788
|
+
tags: scope.saved
|
|
789
|
+
});
|
|
624
790
|
const http = createHttp({
|
|
625
791
|
allowedOrigins,
|
|
626
792
|
ownOrigin,
|
|
@@ -665,13 +831,13 @@ function createJigsServer(options) {
|
|
|
665
831
|
const restoreOnly = async (body, context) => {
|
|
666
832
|
const prepared = await prepareSession(body, context);
|
|
667
833
|
if ("error" in prepared) return prepared;
|
|
668
|
-
const document = prepared.document;
|
|
669
|
-
if (document === null || asksModel(prepared.messages)) return { error: {
|
|
834
|
+
const document = prepared.turn.document;
|
|
835
|
+
if (document === null || asksModel(prepared.turn.messages)) return { error: {
|
|
670
836
|
code: "NOT_FOUND",
|
|
671
837
|
message: "This server has no model, so it cannot answer a turn that asks one."
|
|
672
838
|
} };
|
|
673
839
|
return { async *turn() {
|
|
674
|
-
yield* restoreTurn(
|
|
840
|
+
yield* restoreTurn(prepared.scope.compile, host.components.version, document, true);
|
|
675
841
|
} };
|
|
676
842
|
};
|
|
677
843
|
const hostEvents = options.events;
|
|
@@ -731,9 +897,11 @@ function createJigsServer(options) {
|
|
|
731
897
|
};
|
|
732
898
|
const core = {
|
|
733
899
|
call,
|
|
900
|
+
checkTag: async (row, checkOptions) => checkTagIn(hostScope.rows, row, checkOptions),
|
|
734
901
|
compile,
|
|
735
902
|
data,
|
|
736
903
|
events,
|
|
904
|
+
forUser: async (lookup) => userScope(await scopeFor(lookup)),
|
|
737
905
|
frame,
|
|
738
906
|
handler: createHandler({
|
|
739
907
|
...handlerDeps,
|
|
@@ -745,12 +913,12 @@ function createJigsServer(options) {
|
|
|
745
913
|
promptPrefix: (mode) => mode === "chat" ? chatParts.stable : documentParts.stable,
|
|
746
914
|
version,
|
|
747
915
|
withModel: (modelOptions) => createModelServer({
|
|
748
|
-
checker,
|
|
749
916
|
context: options.context,
|
|
750
917
|
handlerDeps,
|
|
751
918
|
host,
|
|
752
919
|
onError,
|
|
753
920
|
prepareSession,
|
|
921
|
+
scopeFor,
|
|
754
922
|
toolPrompt: toolParts === null ? null : {
|
|
755
923
|
system: `${toolParts.stable}\n\n${toolParts.variable}`,
|
|
756
924
|
systemPrefix: toolParts.stable
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Advice, DesignCheckCode, Placement } from "./design.js";
|
|
2
2
|
import { ConfirmationStore, memoryConfirmations } from "./confirm.js";
|
|
3
|
-
import { CallContext, CallOutcome, CallReport, CallReportOutcome, CallRequest, ChatBlockResult, ChatBlockSource, ChatEvent, ChatOptions, ChatTurn, CompileOptions, ContextFunction, EditFailureReason, ErrorInfo, EventSubscriber, EventsFunction, JigsModelServer, JigsServer, JigsServerOptions, ModelFunction, ModelOptions, ModelRequest, ModelRequestKind, PageCall, PageFunction, PagePolicy, PageResolver, PolicyCall, PushInvalidate, PushInvalidatePages, PushInvalidation, PushPage, PushSend, RequiresAction, ResolvedPage, RunFunctions, SavedPage, SessionRequest, Turn, TurnEdits, TurnRewrite, TurnRound, Usage } from "./types.js";
|
|
3
|
+
import { CallContext, CallOutcome, CallReport, CallReportOutcome, CallRequest, ChatBlockResult, ChatBlockSource, ChatEvent, ChatOptions, ChatTurn, CheckTagOptions, CheckedTag, CompileOptions, ContextFunction, EditFailureReason, ErrorInfo, EventSubscriber, EventsFunction, JigsModelServer, JigsServer, JigsServerOptions, ModelFunction, ModelOptions, ModelRequest, ModelRequestKind, PageCall, PageFunction, PagePolicy, PageResolver, PolicyCall, PushInvalidate, PushInvalidatePages, PushInvalidation, PushPage, PushSend, RequiresAction, ResolvedPage, RunFunctions, SavedPage, SavedTag, SessionRequest, TagResolver, Turn, TurnEdits, TurnRewrite, TurnRound, Usage, UserScope } from "./types.js";
|
|
4
4
|
import { chatBlockText } from "./chat.js";
|
|
5
5
|
import { JigsCompileError, JigsError, ThrownCode } from "./errors.js";
|
|
6
6
|
import { createJigsServer, defineRun } from "./core.js";
|
|
7
7
|
import { addUsage } from "./usage.js";
|
|
8
8
|
import { PromptMode, askOf, withDocument } from "@jigs.design/prompt";
|
|
9
9
|
import { ChatBlock, ChatBlockStatus, Diagnostic, JigsErrorCode, JigsErrorData, Message, PageProps, PageRef, Program, SessionDocument, SessionEvent, Severity, Span } from "@jigs.design/protocol";
|
|
10
|
-
export { type Advice, type CallContext, type CallOutcome, type CallReport, type CallReportOutcome, type CallRequest, type ChatBlock, type ChatBlockResult, type ChatBlockSource, type ChatBlockStatus, type ChatEvent, type ChatOptions, type ChatTurn, type CompileOptions, type ConfirmationStore, type ContextFunction, type DesignCheckCode, type Diagnostic, type EditFailureReason, type ErrorInfo, type EventSubscriber, type EventsFunction, JigsCompileError, JigsError, type JigsErrorCode, type JigsErrorData, type JigsModelServer, type JigsServer, type JigsServerOptions, type Message, type ModelFunction, type ModelOptions, type ModelRequest, type ModelRequestKind, type PageCall, type PageFunction, type PagePolicy, type PageProps, type PageRef, type PageResolver, type Placement, type PolicyCall, type Program, type PromptMode, type PushInvalidate, type PushInvalidatePages, type PushInvalidation, type PushPage, type PushSend, type RequiresAction, type ResolvedPage, type RunFunctions, type SavedPage, type SessionDocument, type SessionEvent, type SessionRequest, type Severity, type Span, type ThrownCode, type Turn, type TurnEdits, type TurnRewrite, type TurnRound, type Usage, addUsage, askOf, chatBlockText, createJigsServer, defineRun, memoryConfirmations, withDocument };
|
|
10
|
+
export { type Advice, type CallContext, type CallOutcome, type CallReport, type CallReportOutcome, type CallRequest, type ChatBlock, type ChatBlockResult, type ChatBlockSource, type ChatBlockStatus, type ChatEvent, type ChatOptions, type ChatTurn, type CheckTagOptions, type CheckedTag, type CompileOptions, type ConfirmationStore, type ContextFunction, type DesignCheckCode, type Diagnostic, type EditFailureReason, type ErrorInfo, type EventSubscriber, type EventsFunction, JigsCompileError, JigsError, type JigsErrorCode, type JigsErrorData, type JigsModelServer, type JigsServer, type JigsServerOptions, type Message, type ModelFunction, type ModelOptions, type ModelRequest, type ModelRequestKind, type PageCall, type PageFunction, type PagePolicy, type PageProps, type PageRef, type PageResolver, type Placement, type PolicyCall, type Program, type PromptMode, type PushInvalidate, type PushInvalidatePages, type PushInvalidation, type PushPage, type PushSend, type RequiresAction, type ResolvedPage, type RunFunctions, type SavedPage, type SavedTag, type SessionDocument, type SessionEvent, type SessionRequest, type Severity, type Span, type TagResolver, type ThrownCode, type Turn, type TurnEdits, type TurnRewrite, type TurnRound, type Usage, type UserScope, addUsage, askOf, chatBlockText, createJigsServer, defineRun, memoryConfirmations, withDocument };
|
package/dist/model-server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { splitPragma } from "./pragma.js";
|
|
|
3
3
|
import { createHandler, issuesText, parseBounded } from "./handler.js";
|
|
4
4
|
import { MAX_CONTENT, runSession } from "./session.js";
|
|
5
5
|
import { createReplyParser, repairBlock, streamChat } from "./chat.js";
|
|
6
|
+
import { withTail } from "./scope.js";
|
|
6
7
|
import { TOOL_RESULTS } from "@jigs.design/prompt";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
//#region src/model-server.ts
|
|
@@ -25,7 +26,10 @@ function createModelServer(internals, core, options) {
|
|
|
25
26
|
const maxRepairs = options.maxRepairs ?? 1;
|
|
26
27
|
const programIntervalMs = options.programIntervalMs ?? 250;
|
|
27
28
|
const designRepair = new Set(options.designRepair ?? []);
|
|
28
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* A session request checked, its options parsed, a named document
|
|
31
|
+
* resolved for `ctx`, and the scope `ctx` compiles and prompts in.
|
|
32
|
+
*/
|
|
29
33
|
const prepareSession = async (body, call) => {
|
|
30
34
|
let turnOptions;
|
|
31
35
|
if (options.options === void 0) turnOptions = void 0;
|
|
@@ -37,33 +41,47 @@ function createModelServer(internals, core, options) {
|
|
|
37
41
|
} };
|
|
38
42
|
turnOptions = parsed.data;
|
|
39
43
|
}
|
|
40
|
-
const
|
|
41
|
-
return "error" in
|
|
44
|
+
const prepared = await internals.prepareSession(body, call);
|
|
45
|
+
return "error" in prepared ? prepared : {
|
|
42
46
|
options: turnOptions,
|
|
43
|
-
|
|
47
|
+
scope: prepared.scope,
|
|
48
|
+
turn: prepared.turn
|
|
44
49
|
};
|
|
45
50
|
};
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
designRepair,
|
|
49
|
-
host: internals.host,
|
|
50
|
-
kind: "session",
|
|
51
|
-
maxRepairs,
|
|
52
|
-
model: options.model,
|
|
53
|
-
onError: internals.onError,
|
|
54
|
-
onTurn: options.onTurn,
|
|
55
|
-
programIntervalMs,
|
|
51
|
+
const toolPrompt = internals.toolPrompt;
|
|
52
|
+
const documentPrompt = {
|
|
56
53
|
system,
|
|
57
54
|
systemPrefix: core.promptPrefix("document")
|
|
58
55
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
/**
|
|
57
|
+
* A turn's deps in `scope`: its compile, and the document prompt (the
|
|
58
|
+
* slot's when `tools` and the server has one) with the scope's saved
|
|
59
|
+
* tags at the end.
|
|
60
|
+
*/
|
|
61
|
+
const sessionDeps = (scope, tools) => {
|
|
62
|
+
const prompt = tools && toolPrompt !== null ? toolPrompt : documentPrompt;
|
|
63
|
+
return {
|
|
64
|
+
compile: scope.compile,
|
|
65
|
+
designRepair,
|
|
66
|
+
host: internals.host,
|
|
67
|
+
kind: "session",
|
|
68
|
+
maxRepairs,
|
|
69
|
+
model: options.model,
|
|
70
|
+
onError: internals.onError,
|
|
71
|
+
onTurn: options.onTurn,
|
|
72
|
+
programIntervalMs,
|
|
73
|
+
savedTags: scope.saved.map((tag) => tag.name),
|
|
74
|
+
system: withTail(prompt.system, scope.tail),
|
|
75
|
+
systemPrefix: prompt.systemPrefix,
|
|
76
|
+
systemTail: scope.tail
|
|
77
|
+
};
|
|
64
78
|
};
|
|
65
|
-
/**
|
|
66
|
-
|
|
79
|
+
/**
|
|
80
|
+
* A session's deps: the slot's prompt when its document reads
|
|
81
|
+
* `toolResults`, compiled in the request's scope, so a document that
|
|
82
|
+
* reads it only through a saved tag gets it too.
|
|
83
|
+
*/
|
|
84
|
+
const depsFor = (turn, scope) => sessionDeps(scope, toolPrompt !== null && turn.document !== null && Object.hasOwn(scope.compile(splitPragma(turn.document).body).program.procedures, TOOL_RESULTS));
|
|
67
85
|
const chat = async (request, chatOptions) => {
|
|
68
86
|
const refused = internals.handlerDeps.http.refusal(request);
|
|
69
87
|
if (refused !== null) throw new JigsError(refused.code, refused.message);
|
|
@@ -92,15 +110,16 @@ function createModelServer(internals, core, options) {
|
|
|
92
110
|
ctx,
|
|
93
111
|
signal: chatOptions?.signal ?? new AbortController().signal
|
|
94
112
|
};
|
|
113
|
+
const scope = await internals.scopeFor(turnCall);
|
|
95
114
|
const repairRounds = Math.max(0, maxRepairs - 1);
|
|
96
115
|
const parser = createReplyParser({
|
|
97
116
|
catalogVersion: internals.host.components.version,
|
|
98
|
-
compile: (body) =>
|
|
117
|
+
compile: (body) => scope.compile(body),
|
|
99
118
|
designRepair,
|
|
100
119
|
now: () => performance.now(),
|
|
101
120
|
programIntervalMs,
|
|
102
121
|
repairs: maxRepairs > 0,
|
|
103
|
-
tags:
|
|
122
|
+
tags: scope.tags
|
|
104
123
|
}, input.data.blocks ?? []);
|
|
105
124
|
const turnDeps = {
|
|
106
125
|
call: turnCall,
|
|
@@ -108,7 +127,7 @@ function createModelServer(internals, core, options) {
|
|
|
108
127
|
parser,
|
|
109
128
|
prompt: input.data.prompt,
|
|
110
129
|
session: {
|
|
111
|
-
...
|
|
130
|
+
...sessionDeps(scope, true),
|
|
112
131
|
kind: "chat-repair",
|
|
113
132
|
maxRepairs: repairRounds
|
|
114
133
|
}
|
|
@@ -124,23 +143,26 @@ function createModelServer(internals, core, options) {
|
|
|
124
143
|
push: parser.push,
|
|
125
144
|
repair
|
|
126
145
|
}, reply, chatOptions?.signal),
|
|
127
|
-
system: chatSystem,
|
|
128
|
-
systemPrefix: core.promptPrefix("chat")
|
|
146
|
+
system: withTail(chatSystem, scope.tail),
|
|
147
|
+
systemPrefix: core.promptPrefix("chat"),
|
|
148
|
+
systemTail: scope.tail
|
|
129
149
|
};
|
|
130
150
|
};
|
|
131
151
|
return {
|
|
132
152
|
call: core.call,
|
|
133
153
|
chat,
|
|
154
|
+
checkTag: core.checkTag,
|
|
134
155
|
compile: core.compile,
|
|
135
156
|
data: core.data,
|
|
136
157
|
events: core.events,
|
|
158
|
+
forUser: core.forUser,
|
|
137
159
|
frame: core.frame,
|
|
138
160
|
handler: createHandler({
|
|
139
161
|
...internals.handlerDeps,
|
|
140
162
|
session: async (body, call) => {
|
|
141
163
|
const prepared = await prepareSession(body, call);
|
|
142
164
|
if ("error" in prepared) return prepared;
|
|
143
|
-
return { turn: (turnCall) => runSession(depsFor(prepared.turn), prepared.turn, prepared.options, turnCall) };
|
|
165
|
+
return { turn: (turnCall) => runSession(depsFor(prepared.turn, prepared.scope), prepared.turn, prepared.options, turnCall) };
|
|
144
166
|
}
|
|
145
167
|
}),
|
|
146
168
|
maxBodyBytes: core.maxBodyBytes,
|
|
@@ -156,7 +178,7 @@ function createModelServer(internals, core, options) {
|
|
|
156
178
|
};
|
|
157
179
|
return;
|
|
158
180
|
}
|
|
159
|
-
yield* runSession(depsFor(prepared.turn), prepared.turn, prepared.options, context);
|
|
181
|
+
yield* runSession(depsFor(prepared.turn, prepared.scope), prepared.turn, prepared.options, context);
|
|
160
182
|
},
|
|
161
183
|
version: core.version
|
|
162
184
|
};
|
package/dist/pragma.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { Diagnostic, Span } from "@jigs.design/protocol";
|
package/dist/scope.d.ts
ADDED
package/dist/scope.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { belowPrefix, splitPragma, versionDiagnostics } from "./pragma.js";
|
|
2
|
+
import { MAX_CONTENT } from "./session.js";
|
|
3
|
+
import { boundedCache } from "./compile-cache.js";
|
|
4
|
+
import { jigSignature } from "@jigs.design/compiler";
|
|
5
|
+
import { scanDefine } from "@jigs.design/host";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/scope.ts
|
|
8
|
+
/** The longest description a saved tag may have, in characters. */
|
|
9
|
+
const MAX_DESCRIPTION = 300;
|
|
10
|
+
/** The longest example a saved tag may have, in characters: sample rows fit. */
|
|
11
|
+
const MAX_EXAMPLE = 1e3;
|
|
12
|
+
/**
|
|
13
|
+
* The longest signature a saved tag may render to, in characters. Expression
|
|
14
|
+
* defaults print as `{…}`, so a signature is its prop names and scalar
|
|
15
|
+
* defaults: a backstop, not a bound a template meets.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_SIGNATURE = 500;
|
|
18
|
+
/** What the scope cache holds at most: 32 MB of sources and tails. */
|
|
19
|
+
const MAX_SCOPE_BYTES = 33554432;
|
|
20
|
+
/** How many scopes the scope cache holds at most. */
|
|
21
|
+
const MAX_SCOPES = 256;
|
|
22
|
+
/** A line terminator, which a description, an example or a signature may not hold. */
|
|
23
|
+
const LINE_BREAK = /[\r\n\u2028\u2029]/;
|
|
24
|
+
/** What `resolveTags` must return each entry as, checked, since a plain-JavaScript one may not. */
|
|
25
|
+
const savedTagSchema = z.object({
|
|
26
|
+
description: z.string(),
|
|
27
|
+
example: z.string().exactOptional(),
|
|
28
|
+
source: z.string()
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* What `resolveTags` must return, checked: an array, each entry a tag or,
|
|
32
|
+
* when it is not one, null, so the scope drops that entry and keeps the rest.
|
|
33
|
+
*/
|
|
34
|
+
const savedTagListSchema = z.array(savedTagSchema.or(z.unknown().transform(() => null)));
|
|
35
|
+
/**
|
|
36
|
+
* The scan of one saved tag before anything compiles: one `<define>` with a
|
|
37
|
+
* tag name that is not `reserved`, and a description, example and rendered
|
|
38
|
+
* signature on one line each and within their limits.
|
|
39
|
+
*/
|
|
40
|
+
function scanTag(row, reserved) {
|
|
41
|
+
if (row.source.length > 2e5) return { message: `A saved tag's source is at most ${MAX_CONTENT} characters` };
|
|
42
|
+
const scanned = scanDefine(row.source);
|
|
43
|
+
if ("reason" in scanned) {
|
|
44
|
+
if (scanned.reason === "document") return { message: "A saved tag's source must be one <define name=\"…\"> and nothing else" };
|
|
45
|
+
return scanned.name === void 0 ? { message: "A saved tag's <define> needs a literal name, e.g. <define name=\"groceryRun\">" } : {
|
|
46
|
+
message: `Tag name "${scanned.name}" must start with a lowercase letter and use letters and digits only`,
|
|
47
|
+
name: scanned.name
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const { name } = scanned;
|
|
51
|
+
if (reserved.has(name)) return {
|
|
52
|
+
message: `"${name}" is taken by the app: a component, a language tag or one of its jigs. Name the tag something else`,
|
|
53
|
+
name
|
|
54
|
+
};
|
|
55
|
+
const lines = [[
|
|
56
|
+
"description",
|
|
57
|
+
row.description,
|
|
58
|
+
MAX_DESCRIPTION
|
|
59
|
+
], [
|
|
60
|
+
"example",
|
|
61
|
+
row.example,
|
|
62
|
+
MAX_EXAMPLE
|
|
63
|
+
]];
|
|
64
|
+
const split = splitPragma(row.source);
|
|
65
|
+
const signature = jigSignature({
|
|
66
|
+
name,
|
|
67
|
+
source: split.body
|
|
68
|
+
}, { elideExpressions: true });
|
|
69
|
+
lines.push([
|
|
70
|
+
"signature",
|
|
71
|
+
signature,
|
|
72
|
+
MAX_SIGNATURE
|
|
73
|
+
]);
|
|
74
|
+
for (const [field, text, limit] of lines) {
|
|
75
|
+
if (text === void 0) continue;
|
|
76
|
+
if (LINE_BREAK.test(text)) return {
|
|
77
|
+
message: `<${name}>'s ${field} must be one line`,
|
|
78
|
+
name
|
|
79
|
+
};
|
|
80
|
+
if (text.length > limit) return {
|
|
81
|
+
message: `<${name}>'s ${field} is longer than ${limit} characters`,
|
|
82
|
+
name
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const tag = {
|
|
86
|
+
body: split.body,
|
|
87
|
+
description: row.description,
|
|
88
|
+
name,
|
|
89
|
+
prefix: row.source.slice(0, row.source.length - split.body.length),
|
|
90
|
+
signature,
|
|
91
|
+
source: row.source,
|
|
92
|
+
versions: split.versions
|
|
93
|
+
};
|
|
94
|
+
if (row.example !== void 0) tag.example = row.example;
|
|
95
|
+
return tag;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* A scanned tag's diagnostics in its row's source: its pragma's against
|
|
99
|
+
* `catalogVersion`, then `found`, numbered by its body, moved below the pragma.
|
|
100
|
+
*/
|
|
101
|
+
function tagDiagnostics(tag, found, catalogVersion) {
|
|
102
|
+
return [...versionDiagnostics(tag.versions, catalogVersion), ...belowPrefix(found, tag.prefix)];
|
|
103
|
+
}
|
|
104
|
+
/** A scanned tag as `checkTag` and `UserScope.tags` show it. */
|
|
105
|
+
function checkedTag(tag, diagnostics) {
|
|
106
|
+
const checked = {
|
|
107
|
+
description: tag.description,
|
|
108
|
+
diagnostics,
|
|
109
|
+
name: tag.name,
|
|
110
|
+
signature: tag.signature
|
|
111
|
+
};
|
|
112
|
+
if (tag.example !== void 0) checked.example = tag.example;
|
|
113
|
+
return checked;
|
|
114
|
+
}
|
|
115
|
+
/** A kept tag as the prompt lists it: its signature read from its body. */
|
|
116
|
+
function promptTag(tag) {
|
|
117
|
+
const listed = {
|
|
118
|
+
description: tag.description,
|
|
119
|
+
name: tag.name,
|
|
120
|
+
source: tag.body
|
|
121
|
+
};
|
|
122
|
+
if (tag.example !== void 0) listed.example = tag.example;
|
|
123
|
+
return listed;
|
|
124
|
+
}
|
|
125
|
+
/** The kept rows as tuples, so no two lists hash alike by concatenation. */
|
|
126
|
+
function scopeHashInput(tags) {
|
|
127
|
+
return JSON.stringify(tags.map((tag) => [
|
|
128
|
+
tag.name,
|
|
129
|
+
tag.source,
|
|
130
|
+
tag.description,
|
|
131
|
+
tag.example ?? null
|
|
132
|
+
]));
|
|
133
|
+
}
|
|
134
|
+
/** A resolved list as tuples, an entry that is not a `SavedTag` as null: the scope cache's key. */
|
|
135
|
+
function listHashInput(rows) {
|
|
136
|
+
return JSON.stringify(rows.map((row) => row === null ? null : [
|
|
137
|
+
row.source,
|
|
138
|
+
row.description,
|
|
139
|
+
row.example ?? null
|
|
140
|
+
]));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Scopes by `${namespace}.${list hash}`, shared by every server in this
|
|
144
|
+
* process, as the compile cache is: bounded by the kept sources and the
|
|
145
|
+
* tail, in characters, and by count. On a hit nothing compiles and nothing
|
|
146
|
+
* reaches `onError` again.
|
|
147
|
+
*/
|
|
148
|
+
const processScopeCache = boundedCache(MAX_SCOPE_BYTES, (scope) => scope.tail.length + scope.rows.reduce((sum, tag) => sum + tag.source.length, 0), MAX_SCOPES);
|
|
149
|
+
/** `system` with the caller's saved tags after it, or as it is with none. */
|
|
150
|
+
function withTail(system, tail) {
|
|
151
|
+
return tail === "" ? system : `${system}\n\n${tail}`;
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { checkedTag, listHashInput, processScopeCache, promptTag, savedTagListSchema, savedTagSchema, scanTag, scopeHashInput, tagDiagnostics, withTail };
|
package/dist/session.js
CHANGED
|
@@ -107,9 +107,9 @@ function applyChecked(base, edits, compile) {
|
|
|
107
107
|
* A turn that starts from a document and asks nothing: its source is
|
|
108
108
|
* checked and compiled again, with no model call.
|
|
109
109
|
*/
|
|
110
|
-
function* restoreTurn(
|
|
110
|
+
function* restoreTurn(compile, catalogVersion, content, fresh) {
|
|
111
111
|
const restored = splitPragma(content);
|
|
112
|
-
const compiled =
|
|
112
|
+
const compiled = compile(restored.body);
|
|
113
113
|
const diagnostics = [...versionDiagnostics(restored.versions, catalogVersion), ...belowPrefix(compiled.diagnostics, restored.versions === null ? pragmaLine(catalogVersion) : content.slice(0, content.length - restored.body.length))];
|
|
114
114
|
yield {
|
|
115
115
|
diagnostics,
|
|
@@ -151,13 +151,13 @@ async function* runSession(deps, turn, options, call) {
|
|
|
151
151
|
if (deps.kind === "chat-repair") return "chat-repair";
|
|
152
152
|
return round === 0 ? "session" : "repair";
|
|
153
153
|
};
|
|
154
|
-
const compile = (body) => deps.
|
|
154
|
+
const compile = (body) => deps.compile(body);
|
|
155
155
|
const placement = deps.kind === "session" ? "document" : "chat";
|
|
156
156
|
/** Whether a round already asked for the design changes `designRepair` names. */
|
|
157
157
|
let designRepaired = false;
|
|
158
158
|
const { messages } = turn;
|
|
159
159
|
if (turn.document !== null && !asksModel(messages)) {
|
|
160
|
-
yield* restoreTurn(deps.
|
|
160
|
+
yield* restoreTurn(deps.compile, catalogVersion, turn.document, true);
|
|
161
161
|
return;
|
|
162
162
|
}
|
|
163
163
|
const conversation = messages.map((message) => message.role === "assistant" ? {
|
|
@@ -226,7 +226,8 @@ async function* runSession(deps, turn, options, call) {
|
|
|
226
226
|
options,
|
|
227
227
|
signal: call.signal,
|
|
228
228
|
system: deps.system,
|
|
229
|
-
systemPrefix: deps.systemPrefix
|
|
229
|
+
systemPrefix: deps.systemPrefix,
|
|
230
|
+
systemTail: deps.systemTail
|
|
230
231
|
})[Symbol.asyncIterator]();
|
|
231
232
|
try {
|
|
232
233
|
for (;;) {
|
|
@@ -404,6 +405,7 @@ async function* runSession(deps, turn, options, call) {
|
|
|
404
405
|
options,
|
|
405
406
|
repairs,
|
|
406
407
|
rounds,
|
|
408
|
+
savedTags: deps.savedTags,
|
|
407
409
|
source: finalSource,
|
|
408
410
|
usage
|
|
409
411
|
});
|
package/dist/types.d.ts
CHANGED
|
@@ -33,7 +33,11 @@ export interface ModelRequest<TContext, TOptions> {
|
|
|
33
33
|
messages: ReadonlyArray<Message>;
|
|
34
34
|
options: TOptions;
|
|
35
35
|
signal: AbortSignal;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The document prompt for this request: the host's, with the server's
|
|
38
|
+
* `instructions`, then the caller's saved tags (`systemTail`) when
|
|
39
|
+
* `resolveTags` returned any.
|
|
40
|
+
*/
|
|
37
41
|
system: string;
|
|
38
42
|
/**
|
|
39
43
|
* The start of `system` that is the same on every turn: the language and
|
|
@@ -41,6 +45,13 @@ export interface ModelRequest<TContext, TOptions> {
|
|
|
41
45
|
* provider's prompt cache, as `aiSdkModel` does.
|
|
42
46
|
*/
|
|
43
47
|
systemPrefix: string;
|
|
48
|
+
/**
|
|
49
|
+
* The end of `system` that differs per user: the `## Saved tags` section
|
|
50
|
+
* for the caller's saved tags, or "" with none. Mark it as a third part
|
|
51
|
+
* (`cachedInstructions(system, systemPrefix, systemTail)`), so the host's
|
|
52
|
+
* part between stays cached for every user.
|
|
53
|
+
*/
|
|
54
|
+
systemTail: string;
|
|
44
55
|
}
|
|
45
56
|
/**
|
|
46
57
|
* The product's call to its LLM. Yield text as it arrives. A generator may
|
|
@@ -142,6 +153,11 @@ export interface Turn<TContext, TOptions> {
|
|
|
142
153
|
* a stop or a failure is not a round.
|
|
143
154
|
*/
|
|
144
155
|
rounds: ReadonlyArray<TurnRound>;
|
|
156
|
+
/**
|
|
157
|
+
* The names of the caller's saved tags the prompt listed, in its order;
|
|
158
|
+
* empty without `resolveTags`, or when it returned none.
|
|
159
|
+
*/
|
|
160
|
+
savedTags: ReadonlyArray<string>;
|
|
145
161
|
/** The checked source sent with `done`, with its pragma; null when the turn did not finish. */
|
|
146
162
|
source: string | null;
|
|
147
163
|
usage: Usage;
|
|
@@ -281,13 +297,17 @@ export interface EventSubscriber<TProcedures extends Procedures, TContext, TPage
|
|
|
281
297
|
*/
|
|
282
298
|
export type EventsFunction<TProcedures extends Procedures, TContext, TPageName extends string = string> = (subscriber: EventSubscriber<TProcedures, TContext, TPageName>) => (() => void) | Promise<() => void>;
|
|
283
299
|
export interface ErrorInfo {
|
|
284
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* The procedure, for a call; the page, for a page; the saved tag, for a
|
|
302
|
+
* tag, when the entry had a name to read.
|
|
303
|
+
*/
|
|
285
304
|
name?: string;
|
|
286
305
|
/**
|
|
287
306
|
* The request it came from: a call, a session turn, a chat turn with its
|
|
288
|
-
* repairs, the push stream,
|
|
307
|
+
* repairs, the push stream, a page (`jigs.page` or its wire request), or
|
|
308
|
+
* a saved tag (`resolveTags`, or one entry it returned that was dropped).
|
|
289
309
|
*/
|
|
290
|
-
type: "call" | "chat" | "events" | "page" | "session";
|
|
310
|
+
type: "call" | "chat" | "events" | "page" | "session" | "tag";
|
|
291
311
|
}
|
|
292
312
|
/** A page `resolvePage` found: the source as the app stored it, and what it reads. */
|
|
293
313
|
export interface SavedPage {
|
|
@@ -311,6 +331,89 @@ export type PageResolver<TContext> = (name: string, call: {
|
|
|
311
331
|
ctx: TContext;
|
|
312
332
|
signal?: AbortSignal;
|
|
313
333
|
}) => Promise<SavedPage | null> | SavedPage | null;
|
|
334
|
+
/**
|
|
335
|
+
* A tag a user saved, as the app stores it: one `<define>`, which names
|
|
336
|
+
* it, and what the model is told about it. A saved tag is never a page.
|
|
337
|
+
*/
|
|
338
|
+
export interface SavedTag {
|
|
339
|
+
/**
|
|
340
|
+
* One line for the model: when to reach for this tag. At most 300
|
|
341
|
+
* characters, with no line break.
|
|
342
|
+
*/
|
|
343
|
+
description: string;
|
|
344
|
+
/** A one-line use, shown to the model. At most 300 characters. */
|
|
345
|
+
example?: string;
|
|
346
|
+
/**
|
|
347
|
+
* One `<define>` and nothing else, with or without a pragma line; at
|
|
348
|
+
* most 200,000 characters.
|
|
349
|
+
*/
|
|
350
|
+
source: string;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* The caller's saved tags, for the `ctx` that `context` returned, in the
|
|
354
|
+
* order the prompt lists them. Runs on every chat turn, session turn,
|
|
355
|
+
* saved-page request and `forUser`; cache in it if the store is slow.
|
|
356
|
+
* Throw a `JigsError` to refuse the request.
|
|
357
|
+
*/
|
|
358
|
+
export type TagResolver<TContext> = (call: {
|
|
359
|
+
ctx: TContext;
|
|
360
|
+
signal?: AbortSignal;
|
|
361
|
+
}) => Promise<ReadonlyArray<SavedTag>> | ReadonlyArray<SavedTag>;
|
|
362
|
+
/** A saved tag checked: what the prompt shows for it, and what the checks found. */
|
|
363
|
+
export interface CheckedTag {
|
|
364
|
+
description: string;
|
|
365
|
+
/**
|
|
366
|
+
* What the checks found in its body, numbered by the lines of its
|
|
367
|
+
* source, pragma included: the compiler's diagnostics, a procedure the
|
|
368
|
+
* host lacks, a wrong literal input, a pragma for another catalog.
|
|
369
|
+
*/
|
|
370
|
+
diagnostics: ReadonlyArray<Diagnostic>;
|
|
371
|
+
example?: string;
|
|
372
|
+
/** The name its `<define>` gives it. */
|
|
373
|
+
name: string;
|
|
374
|
+
/** The heading the prompt shows, such as `<groceryRun week>`. */
|
|
375
|
+
signature: string;
|
|
376
|
+
}
|
|
377
|
+
export interface CheckTagOptions {
|
|
378
|
+
/**
|
|
379
|
+
* Reject with a `JigsCompileError` when the tag's body has an error or
|
|
380
|
+
* warning diagnostic. Default true; pass false to get the diagnostics
|
|
381
|
+
* instead.
|
|
382
|
+
*/
|
|
383
|
+
strict?: boolean;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* The server for one caller: the host extended by the tags `resolveTags`
|
|
387
|
+
* returned for their `ctx`, resolved once. Pass it where a server is taken
|
|
388
|
+
* for compiling, such as `reviveJigs`.
|
|
389
|
+
*/
|
|
390
|
+
export interface UserScope {
|
|
391
|
+
/**
|
|
392
|
+
* Checks `tag` as the scope would list it, against the host and this
|
|
393
|
+
* caller's other saved tags, a saved tag of its name replaced by it.
|
|
394
|
+
* Rejects with `JigsError("BAD_INPUT")` for what the scope would drop
|
|
395
|
+
* (not one `<define>`, a name that is not a tag name or is the catalog's,
|
|
396
|
+
* the language's or the host's, a description, example or signature
|
|
397
|
+
* with a line break or over its limit, a source over 200,000
|
|
398
|
+
* characters), and, when `strict`, with a `JigsCompileError` for an error
|
|
399
|
+
* or warning diagnostic.
|
|
400
|
+
*/
|
|
401
|
+
checkTag: (tag: SavedTag, options?: CheckTagOptions) => Promise<CheckedTag>;
|
|
402
|
+
/**
|
|
403
|
+
* `jigs.compile` in this caller's scope: their saved tags compile into the
|
|
404
|
+
* programs that use them. Strict unless told otherwise.
|
|
405
|
+
*/
|
|
406
|
+
compile: JigsServer<unknown>["compile"];
|
|
407
|
+
/** `jigs.prompt(mode)` with this caller's `## Saved tags` at the end. */
|
|
408
|
+
prompt: (mode?: PromptMode) => string;
|
|
409
|
+
/** `jigs.promptPrefix(mode)`: the same for every caller. */
|
|
410
|
+
promptPrefix: (mode?: PromptMode) => string;
|
|
411
|
+
/**
|
|
412
|
+
* The saved tags the scope kept, in the order the prompt lists them,
|
|
413
|
+
* with their lenient diagnostics.
|
|
414
|
+
*/
|
|
415
|
+
tags: ReadonlyArray<CheckedTag>;
|
|
416
|
+
}
|
|
314
417
|
/**
|
|
315
418
|
* Whether a shipped page may be served on the wire to the caller `ctx`
|
|
316
419
|
* names. Saved pages are scoped by `resolvePage` instead.
|
|
@@ -392,6 +495,16 @@ export interface JigsServerOptions<TProcedures extends Procedures, TCatalog exte
|
|
|
392
495
|
* call says `strict`. See `PageResolver`.
|
|
393
496
|
*/
|
|
394
497
|
resolvePage?: PageResolver<NoInfer<TContext>>;
|
|
498
|
+
/**
|
|
499
|
+
* The caller's saved tags, on every request that compiles or prompts:
|
|
500
|
+
* chat and session turns, saved pages and `forUser`. Each request builds
|
|
501
|
+
* its scope from them: the host's tags extended by the user's, compiled
|
|
502
|
+
* leniently, listed as `## Saved tags` at the end of the prompt, and
|
|
503
|
+
* part of the compile cache's key. A bad entry is dropped and reported
|
|
504
|
+
* to `onError` with `type: "tag"`; past 50 tags or 200,000 characters of
|
|
505
|
+
* source, the rest are. Shipped pages never see them. See `TagResolver`.
|
|
506
|
+
*/
|
|
507
|
+
resolveTags?: TagResolver<NoInfer<TContext>>;
|
|
395
508
|
run: RunFunctions<NoInfer<TProcedures>, NoInfer<TContext>>;
|
|
396
509
|
/**
|
|
397
510
|
* Signs confirmation tokens, at least 32 characters; keep it out of the
|
|
@@ -601,10 +714,15 @@ export interface ChatTurn {
|
|
|
601
714
|
results: () => Array<ChatBlockResult>;
|
|
602
715
|
/** `push`, `end` and `repair` together; ends after every repair settles. */
|
|
603
716
|
stream: (reply: AsyncIterable<string>) => AsyncIterable<ChatEvent>;
|
|
604
|
-
/**
|
|
717
|
+
/**
|
|
718
|
+
* The chat-mode prompt for this request: `prompt("chat")`, with the
|
|
719
|
+
* server's `instructions`, then the caller's saved tags (`systemTail`).
|
|
720
|
+
*/
|
|
605
721
|
system: string;
|
|
606
722
|
/** The start of `system` that is the same on every turn, as on `ModelRequest`. */
|
|
607
723
|
systemPrefix: string;
|
|
724
|
+
/** The end of `system` that differs per user, or "", as on `ModelRequest`. */
|
|
725
|
+
systemTail: string;
|
|
608
726
|
}
|
|
609
727
|
/** A page, compiled, and what the checks said about it. */
|
|
610
728
|
export interface ResolvedPage {
|
|
@@ -665,6 +783,11 @@ export interface PageFunction<TContext, TPageName extends string> {
|
|
|
665
783
|
}
|
|
666
784
|
export interface JigsServer<TContext, TPageName extends string = never> {
|
|
667
785
|
call: (request: CallRequest, context: CallContext<TContext>) => Promise<CallOutcome>;
|
|
786
|
+
/**
|
|
787
|
+
* Checks a saved tag against the host alone, as `UserScope.checkTag`
|
|
788
|
+
* does in a caller's scope.
|
|
789
|
+
*/
|
|
790
|
+
checkTag: (tag: SavedTag, options?: CheckTagOptions) => Promise<CheckedTag>;
|
|
668
791
|
/** Checks and compiles a source with no model. Strict unless told otherwise. */
|
|
669
792
|
compile: (source: string, options?: CompileOptions) => Promise<{
|
|
670
793
|
/** The design checks' advice, numbered like `diagnostics`. */
|
|
@@ -692,6 +815,17 @@ export interface JigsServer<TContext, TPageName extends string = never> {
|
|
|
692
815
|
* accepts `text/event-stream`, after `context`.
|
|
693
816
|
*/
|
|
694
817
|
events: (request: Request, context: CallContext<TContext>) => Promise<Response>;
|
|
818
|
+
/**
|
|
819
|
+
* The scope for one caller, for server-side code that has their `ctx`:
|
|
820
|
+
* `resolveTags` runs once, and the scope compiles, checks tags and
|
|
821
|
+
* renders prompts with their saved tags. Without `resolveTags`, the host
|
|
822
|
+
* alone. Rejects as `resolveTags` fails: a `JigsError` it throws, or
|
|
823
|
+
* `INTERNAL`.
|
|
824
|
+
*/
|
|
825
|
+
forUser: (call: {
|
|
826
|
+
ctx: TContext;
|
|
827
|
+
signal?: AbortSignal;
|
|
828
|
+
}) => Promise<UserScope>;
|
|
695
829
|
/** The sandbox frame document, with its own content security policy. */
|
|
696
830
|
frame: (request?: Request) => Response;
|
|
697
831
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jigs.design/server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
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.3.
|
|
33
|
-
"@jigs.design/host": "0.3.
|
|
34
|
-
"@jigs.design/prompt": "0.3.
|
|
35
|
-
"@jigs.design/protocol": "0.3.
|
|
36
|
-
"@jigs.design/runtime": "0.3.
|
|
32
|
+
"@jigs.design/compiler": "0.3.7",
|
|
33
|
+
"@jigs.design/host": "0.3.7",
|
|
34
|
+
"@jigs.design/prompt": "0.3.7",
|
|
35
|
+
"@jigs.design/protocol": "0.3.7",
|
|
36
|
+
"@jigs.design/runtime": "0.3.7"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"zod": "^4.0.0"
|