Spaces:
Running
Running
Nicolas Rabault Claude Sonnet 4.6 commited on
Commit ·
54df78b
1
Parent(s): ba2cfd8
feat(engine): loader for narrator JSON
Browse filesCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
package-lock.json
CHANGED
|
@@ -770,6 +770,17 @@
|
|
| 770 |
"dev": true,
|
| 771 |
"license": "MIT"
|
| 772 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 773 |
"node_modules/@vitest/expect": {
|
| 774 |
"version": "2.1.9",
|
| 775 |
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
|
@@ -1273,12 +1284,20 @@
|
|
| 1273 |
"node": ">=14.17"
|
| 1274 |
}
|
| 1275 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1276 |
"node_modules/vite": {
|
| 1277 |
"version": "5.4.21",
|
| 1278 |
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
| 1279 |
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
| 1280 |
"dev": true,
|
| 1281 |
"license": "MIT",
|
|
|
|
| 1282 |
"dependencies": {
|
| 1283 |
"esbuild": "^0.21.3",
|
| 1284 |
"postcss": "^8.4.43",
|
|
@@ -1442,6 +1461,7 @@
|
|
| 1442 |
"packages/story-engine": {
|
| 1443 |
"version": "0.1.0",
|
| 1444 |
"devDependencies": {
|
|
|
|
| 1445 |
"vitest": "^2.1.0"
|
| 1446 |
}
|
| 1447 |
}
|
|
|
|
| 770 |
"dev": true,
|
| 771 |
"license": "MIT"
|
| 772 |
},
|
| 773 |
+
"node_modules/@types/node": {
|
| 774 |
+
"version": "25.6.0",
|
| 775 |
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
| 776 |
+
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
| 777 |
+
"dev": true,
|
| 778 |
+
"license": "MIT",
|
| 779 |
+
"peer": true,
|
| 780 |
+
"dependencies": {
|
| 781 |
+
"undici-types": "~7.19.0"
|
| 782 |
+
}
|
| 783 |
+
},
|
| 784 |
"node_modules/@vitest/expect": {
|
| 785 |
"version": "2.1.9",
|
| 786 |
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
|
|
|
| 1284 |
"node": ">=14.17"
|
| 1285 |
}
|
| 1286 |
},
|
| 1287 |
+
"node_modules/undici-types": {
|
| 1288 |
+
"version": "7.19.2",
|
| 1289 |
+
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
| 1290 |
+
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
| 1291 |
+
"dev": true,
|
| 1292 |
+
"license": "MIT"
|
| 1293 |
+
},
|
| 1294 |
"node_modules/vite": {
|
| 1295 |
"version": "5.4.21",
|
| 1296 |
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
| 1297 |
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
| 1298 |
"dev": true,
|
| 1299 |
"license": "MIT",
|
| 1300 |
+
"peer": true,
|
| 1301 |
"dependencies": {
|
| 1302 |
"esbuild": "^0.21.3",
|
| 1303 |
"postcss": "^8.4.43",
|
|
|
|
| 1461 |
"packages/story-engine": {
|
| 1462 |
"version": "0.1.0",
|
| 1463 |
"devDependencies": {
|
| 1464 |
+
"@types/node": "^25.6.0",
|
| 1465 |
"vitest": "^2.1.0"
|
| 1466 |
}
|
| 1467 |
}
|
packages/story-engine/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
| 12 |
"test:watch": "vitest"
|
| 13 |
},
|
| 14 |
"devDependencies": {
|
|
|
|
| 15 |
"vitest": "^2.1.0"
|
| 16 |
}
|
| 17 |
}
|
|
|
|
| 12 |
"test:watch": "vitest"
|
| 13 |
},
|
| 14 |
"devDependencies": {
|
| 15 |
+
"@types/node": "^25.6.0",
|
| 16 |
"vitest": "^2.1.0"
|
| 17 |
}
|
| 18 |
}
|
packages/story-engine/src/loader.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Narrator } from "./types.js";
|
| 2 |
+
|
| 3 |
+
interface RawNarrator {
|
| 4 |
+
name: string;
|
| 5 |
+
description: string;
|
| 6 |
+
introduction: string;
|
| 7 |
+
background: string;
|
| 8 |
+
goal: string;
|
| 9 |
+
voice: string;
|
| 10 |
+
voice_tone: string;
|
| 11 |
+
// legacy fields ignored: picture, tools, etc.
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export function loadNarrator(raw: unknown): Narrator {
|
| 15 |
+
const r = raw as RawNarrator;
|
| 16 |
+
if (!r || typeof r !== "object") {
|
| 17 |
+
throw new Error("Narrator JSON must be an object");
|
| 18 |
+
}
|
| 19 |
+
for (const field of ["name", "description", "introduction", "background", "goal", "voice", "voice_tone"] as const) {
|
| 20 |
+
if (typeof r[field] !== "string" || !r[field]) {
|
| 21 |
+
throw new Error(`Narrator missing required string field: ${field}`);
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
return {
|
| 25 |
+
id: "narrator",
|
| 26 |
+
name: r.name,
|
| 27 |
+
description: r.description,
|
| 28 |
+
introduction: r.introduction,
|
| 29 |
+
background: r.background,
|
| 30 |
+
goal: r.goal,
|
| 31 |
+
voice: r.voice,
|
| 32 |
+
voiceTone: r.voice_tone,
|
| 33 |
+
};
|
| 34 |
+
}
|
packages/story-engine/tests/fixtures/narrator/narrator.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "The Narrator",
|
| 3 |
+
"description": "A cheerful storyteller",
|
| 4 |
+
"introduction": "Welcome adventurer!",
|
| 5 |
+
"background": "An entity who guides players through stories.",
|
| 6 |
+
"goal": "Help the player pick a story.",
|
| 7 |
+
"voice": "cedar",
|
| 8 |
+
"voice_tone": "Warm, enthusiastic, brief."
|
| 9 |
+
}
|
packages/story-engine/tests/loader.test.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from "vitest";
|
| 2 |
+
import { readFileSync } from "node:fs";
|
| 3 |
+
import { resolve } from "node:path";
|
| 4 |
+
import { loadNarrator } from "../src/loader.js";
|
| 5 |
+
|
| 6 |
+
const narratorJson = JSON.parse(
|
| 7 |
+
readFileSync(resolve(__dirname, "fixtures/narrator/narrator.json"), "utf-8"),
|
| 8 |
+
);
|
| 9 |
+
|
| 10 |
+
describe("loadNarrator", () => {
|
| 11 |
+
it("normalizes a narrator JSON into the Narrator type", () => {
|
| 12 |
+
const narrator = loadNarrator(narratorJson);
|
| 13 |
+
expect(narrator.id).toBe("narrator");
|
| 14 |
+
expect(narrator.name).toBe("The Narrator");
|
| 15 |
+
expect(narrator.voice).toBe("cedar");
|
| 16 |
+
expect(narrator.voiceTone).toBe("Warm, enthusiastic, brief.");
|
| 17 |
+
});
|
| 18 |
+
});
|
packages/story-engine/tsconfig.json
CHANGED
|
@@ -1,4 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"extends": "../../tsconfig.base.json",
|
|
|
|
|
|
|
|
|
|
| 3 |
"include": ["src/**/*", "tests/**/*"]
|
| 4 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"extends": "../../tsconfig.base.json",
|
| 3 |
+
"compilerOptions": {
|
| 4 |
+
"types": ["node"]
|
| 5 |
+
},
|
| 6 |
"include": ["src/**/*", "tests/**/*"]
|
| 7 |
}
|