@inhuman.tools/harness-scheme 0.0.0-stage → 0.2.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/LICENSE.md +7 -0
- package/dist/bound-tools.d.ts +6 -0
- package/dist/bound-tools.d.ts.map +1 -0
- package/dist/bound-tools.js +88 -0
- package/dist/bound-tools.js.map +1 -0
- package/dist/catalog-description.d.ts +5 -0
- package/dist/catalog-description.d.ts.map +1 -0
- package/dist/catalog-description.js +150 -0
- package/dist/catalog-description.js.map +1 -0
- package/dist/connection-models.d.ts +20 -0
- package/dist/connection-models.d.ts.map +1 -0
- package/dist/connection-models.js +31 -0
- package/dist/connection-models.js.map +1 -0
- package/dist/env.d.ts +109 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +923 -0
- package/dist/env.js.map +1 -0
- package/dist/file-path.d.ts +2 -0
- package/dist/file-path.d.ts.map +1 -0
- package/dist/file-path.js +2 -0
- package/dist/file-path.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/interpret.d.ts +13 -0
- package/dist/interpret.d.ts.map +1 -0
- package/dist/interpret.js +26 -0
- package/dist/interpret.js.map +1 -0
- package/dist/keywords.d.ts +17 -0
- package/dist/keywords.d.ts.map +1 -0
- package/dist/keywords.js +76 -0
- package/dist/keywords.js.map +1 -0
- package/dist/limits.d.ts +11 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +4 -0
- package/dist/limits.js.map +1 -0
- package/dist/program-source.d.ts +2 -0
- package/dist/program-source.d.ts.map +1 -0
- package/dist/program-source.js +2 -0
- package/dist/program-source.js.map +1 -0
- package/dist/replay.d.ts +9 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +38 -0
- package/dist/replay.js.map +1 -0
- package/package.json +80 -3
- package/src/bound-tools.ts +108 -0
- package/src/catalog-description.ts +150 -0
- package/src/connection-models.ts +39 -0
- package/src/env.ts +1185 -0
- package/src/file-path.ts +1 -0
- package/src/index.ts +17 -0
- package/src/interpret.ts +26 -0
- package/src/keywords.ts +84 -0
- package/src/limits.ts +13 -0
- package/src/program-source.ts +1 -0
- package/src/replay.ts +46 -0
- package/README.md +0 -3
package/src/file-path.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { filePathOf, stampFilePath } from "@inhuman.tools/scheme-program";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from "./env.js";
|
|
2
|
+
|
|
3
|
+
export * from "./interpret.js";
|
|
4
|
+
|
|
5
|
+
export * from "./keywords.js";
|
|
6
|
+
|
|
7
|
+
export * from "./bound-tools.js";
|
|
8
|
+
|
|
9
|
+
export * from "./catalog-description.js";
|
|
10
|
+
|
|
11
|
+
export * from "./connection-models.js";
|
|
12
|
+
|
|
13
|
+
export * from "./program-source.js";
|
|
14
|
+
|
|
15
|
+
export * from "./limits.js";
|
|
16
|
+
|
|
17
|
+
export * from "./file-path.js";
|
package/src/interpret.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProgrammaticJob body. SessionTurn waits on the bag. Not PEW and not
|
|
3
|
+
* catalog `execute`.
|
|
4
|
+
*
|
|
5
|
+
* `runProgram` and `runWatch` own the evaluation. This builds the ProgramHost
|
|
6
|
+
* — vocabulary lands on the first `prepare` — and calls one of them with the
|
|
7
|
+
* job. A watch job must take `runWatch`: the first prepare wins, and only
|
|
8
|
+
* that call roots meta-reactivity.
|
|
9
|
+
*/
|
|
10
|
+
import { ProgrammaticJob, SCHEME_WATCH_TOOL_NAME } from "@inhuman.tools/harness-model";
|
|
11
|
+
import { runProgram } from "@inhuman.tools/scheme-program";
|
|
12
|
+
import { programSourceLoader } from "@inhuman.tools/scheme-program/fs";
|
|
13
|
+
import { runWatch } from "@inhuman.tools/scheme-watch";
|
|
14
|
+
|
|
15
|
+
import { programHostOf, type SchemeReplHost } from "./env.js";
|
|
16
|
+
|
|
17
|
+
export async function interpretSchemeRepl(chunk: ProgrammaticJob, host: SchemeReplHost): Promise<void> {
|
|
18
|
+
if (host.loadSource == null && host.replayOnly !== true) {
|
|
19
|
+
Object.assign(host, {
|
|
20
|
+
loadSource: (root: string, job: ProgrammaticJob) => programSourceLoader(root, job),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const programHost = programHostOf(host);
|
|
24
|
+
if (chunk.toolName === SCHEME_WATCH_TOOL_NAME) return runWatch(chunk, programHost);
|
|
25
|
+
return runProgram(chunk, programHost);
|
|
26
|
+
}
|
package/src/keywords.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arrival binds a record `inputRest` as keywords only and drops the positional
|
|
3
|
+
* `input`. A verb that takes both declares an `sz.dynamic` rest and splits it
|
|
4
|
+
* here: the first keyword ends the positionals, and each keyword decodes by
|
|
5
|
+
* its own schema, so a default or an optional behaves as in a record rest.
|
|
6
|
+
*/
|
|
7
|
+
import { ASymbol } from "@here.build/arrival/reflect-internals";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
export type KeywordShape = Readonly<Record<string, z.ZodType>>;
|
|
11
|
+
|
|
12
|
+
export type Keywords<S extends KeywordShape> = {
|
|
13
|
+
readonly [K in keyof S]: z.output<S[K]>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function isKeyword(value: unknown): boolean {
|
|
17
|
+
return value instanceof ASymbol && String(value).startsWith(":");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function splitKeywords<S extends KeywordShape>(
|
|
21
|
+
verb: string,
|
|
22
|
+
args: readonly unknown[],
|
|
23
|
+
shape: S,
|
|
24
|
+
): { readonly positionals: readonly unknown[]; readonly keywords: Keywords<S> } {
|
|
25
|
+
const cut = args.findIndex((arg) => isKeyword(arg));
|
|
26
|
+
const positionals = cut === -1 ? [...args] : args.slice(0, cut);
|
|
27
|
+
while (positionals.length > 0 && positionals.at(-1) === undefined) positionals.pop();
|
|
28
|
+
const tail = cut === -1 ? [] : args.slice(cut).filter((arg) => arg !== undefined);
|
|
29
|
+
if (tail.length % 2 !== 0) throw new Error(`${verb}: dangling keyword ${String(tail.at(-1))}`);
|
|
30
|
+
const raw = new Map<string, unknown>();
|
|
31
|
+
for (let i = 0; i < tail.length; i += 2) {
|
|
32
|
+
const key = tail[i];
|
|
33
|
+
if (!isKeyword(key)) throw new Error(`${verb}: expected a keyword, got ${String(key)}`);
|
|
34
|
+
const name = String(key).slice(1);
|
|
35
|
+
if (!Object.hasOwn(shape, name)) {
|
|
36
|
+
const known = Object.keys(shape).map((field) => `:${field}`);
|
|
37
|
+
throw new Error(`${verb}: unknown keyword :${name}${known.length > 0 ? ` (known: ${known.join(", ")})` : ""}`);
|
|
38
|
+
}
|
|
39
|
+
if (raw.has(name)) throw new Error(`${verb}: keyword :${name} given twice`);
|
|
40
|
+
raw.set(name, tail[i + 1]);
|
|
41
|
+
}
|
|
42
|
+
const keywords: Record<string, unknown> = {};
|
|
43
|
+
for (const [name, schema] of Object.entries(shape)) {
|
|
44
|
+
try {
|
|
45
|
+
keywords[name] = schema.decode(raw.get(name));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const text =
|
|
48
|
+
error instanceof z.ZodError
|
|
49
|
+
? error.issues.map((issue) => issue.message).join("; ")
|
|
50
|
+
: error instanceof Error
|
|
51
|
+
? error.message
|
|
52
|
+
: String(error);
|
|
53
|
+
throw new Error(`${verb}: :${name} — ${text}`, { cause: error });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { positionals, keywords: keywords as Keywords<S> };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The rest after a verb's fixed positionals. `:k v` pairs, or one trailing
|
|
61
|
+
* dict — the catalog teaches the dict form, and `String(dict)` is
|
|
62
|
+
* "[object Object]", which is how a rejected dict used to surface.
|
|
63
|
+
*/
|
|
64
|
+
export function keywordsOf<S extends KeywordShape>(verb: string, rest: readonly unknown[], shape: S): Keywords<S> {
|
|
65
|
+
if (rest.length === 1 && isPlainRecord(rest[0]) && !isKeyword(rest[0])) {
|
|
66
|
+
return keywordsOf(verb, recordKeywords(rest[0]), shape);
|
|
67
|
+
}
|
|
68
|
+
const { positionals, keywords } = splitKeywords(verb, rest, shape);
|
|
69
|
+
if (positionals.length > 0) throw new Error(`${verb}: unexpected argument ${String(positionals[0])}`);
|
|
70
|
+
return keywords;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
74
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function recordKeywords(record: Record<string, unknown>): unknown[] {
|
|
78
|
+
const pairs: unknown[] = [];
|
|
79
|
+
for (const [name, value] of Object.entries(record)) {
|
|
80
|
+
if (value === undefined) continue;
|
|
81
|
+
pairs.push(new ASymbol(`:${name}`), value);
|
|
82
|
+
}
|
|
83
|
+
return pairs;
|
|
84
|
+
}
|
package/src/limits.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scheme_repl / scheme_watch bounds. Time and inner-call budgets are
|
|
3
|
+
* opt-in: omit `timeoutMs` / `maxInternalCalls` for no limit. Eval-turn
|
|
4
|
+
* uses the same contract — no host ceiling.
|
|
5
|
+
*/
|
|
6
|
+
export interface SchemeReplLimits {
|
|
7
|
+
/** Binary content blocks kept from one inner tool result. */
|
|
8
|
+
readonly maxAttachments: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_SCHEME_REPL_LIMITS: SchemeReplLimits = Object.freeze({
|
|
12
|
+
maxAttachments: 4,
|
|
13
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { programSourceLoader } from "@inhuman.tools/scheme-program/fs";
|
package/src/replay.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebuild one program's trace from the children on the job. The daemon does
|
|
3
|
+
* not serve a graph: the same verbs run here, and a crossing reads the child
|
|
4
|
+
* it stamped instead of admitting a new one.
|
|
5
|
+
*/
|
|
6
|
+
import type { EvalTrace } from "@here.build/arrival/provenance";
|
|
7
|
+
import { ProgrammaticJob, SCHEME_WATCH_TOOL_NAME } from "@inhuman.tools/harness-model";
|
|
8
|
+
import { replayEvaluation } from "@inhuman.tools/scheme-program";
|
|
9
|
+
import { watchHost } from "@inhuman.tools/scheme-watch";
|
|
10
|
+
import type { BoundTool } from "@inhuman.tools/substrate/bound-tool";
|
|
11
|
+
import { DoorSession } from "@inhuman.tools/substrate/doors";
|
|
12
|
+
|
|
13
|
+
import { programHostOf, type SchemeReplHost } from "./env.js";
|
|
14
|
+
|
|
15
|
+
function toolsFrom(job: ProgrammaticJob): Map<string, BoundTool> {
|
|
16
|
+
const tools = new Map<string, BoundTool>();
|
|
17
|
+
for (const name of Object.keys(job.boundTools)) {
|
|
18
|
+
if (name === "") continue;
|
|
19
|
+
tools.set(name, {
|
|
20
|
+
qualifiedName: name,
|
|
21
|
+
slug: "",
|
|
22
|
+
tool: name,
|
|
23
|
+
signature: () => ({ params: [], signatureText: name }),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return tools;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function replayScheme(job: ProgrammaticJob, trace: EvalTrace, signal: AbortSignal): Promise<void> {
|
|
30
|
+
const source = job.args?.source;
|
|
31
|
+
const host: SchemeReplHost = {
|
|
32
|
+
replayOnly: true,
|
|
33
|
+
replayTools: toolsFrom(job),
|
|
34
|
+
root: "",
|
|
35
|
+
doors: new DoorSession(() => {}),
|
|
36
|
+
signal,
|
|
37
|
+
evalProgram: typeof source === "string",
|
|
38
|
+
admit() {
|
|
39
|
+
throw new Error("replay does not admit");
|
|
40
|
+
},
|
|
41
|
+
observe: (run) => run(trace),
|
|
42
|
+
};
|
|
43
|
+
const program = programHostOf(host);
|
|
44
|
+
const watching = job.toolName === SCHEME_WATCH_TOOL_NAME ? watchHost(program) : program;
|
|
45
|
+
await replayEvaluation(job, watching, trace);
|
|
46
|
+
}
|
package/README.md
DELETED