@moikapy/origen 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/src/types.ts ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Origen types — no runtime deps, safe for client + server.
3
+ */
4
+
5
+ /** D1-compatible database interface for tool execution */
6
+ export interface D1Like {
7
+ prepare(sql: string): {
8
+ bind(...params: unknown[]): {
9
+ all(): Promise<{ results?: Record<string, unknown>[] }>;
10
+ first(): Promise<Record<string, unknown> | null>;
11
+ run(): Promise<{ meta?: { changes: number; last_row_id: number } }>;
12
+ };
13
+ };
14
+ }
15
+
16
+ /** Function that provides a D1 instance to tool executors */
17
+ export type D1Provider = () => Promise<D1Like>;
18
+
19
+ /** Chat context passed from the UI (what the user is reading) */
20
+ export interface ReadingContext {
21
+ translation: string;
22
+ bookCode: string;
23
+ chapter: number;
24
+ selectedVerses?: number[];
25
+ }
26
+
27
+ /** Agent message with optional metadata */
28
+ export interface AgentMessage {
29
+ role: "user" | "assistant" | "system";
30
+ content: string;
31
+ }
32
+
33
+ /** SSE events streamed from the agent to the UI */
34
+ export type StreamEvent =
35
+ | { type: "thinking"; content: string }
36
+ | { type: "tool_call"; name: string; args: Record<string, unknown> }
37
+ | { type: "tool_result"; name: string; result: string }
38
+ | { type: "text"; content: string }
39
+ | { type: "done"; message: string; citations: Citation[]; usage?: UsageInfo }
40
+ | { type: "error"; message: string };
41
+
42
+ export interface Citation {
43
+ book: string;
44
+ chapter: number;
45
+ verse: number;
46
+ }
47
+
48
+ export interface UsageInfo {
49
+ promptTokens?: number;
50
+ completionTokens?: number;
51
+ totalCost?: number;
52
+ }
53
+
54
+ /** Model configuration entry */
55
+ export interface ModelConfig {
56
+ name: string;
57
+ description: string;
58
+ free: boolean;
59
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Origen v0.3 — unit tests for the core engine.
3
+ * Tests model resolution, Soul.md parsing, message conversion, auth, and tool adapter wiring.
4
+ * No external dependencies — scholar-tools tests live in the scholar monorepo.
5
+ */
6
+
7
+ import { describe, test, expect } from "vitest";
8
+ import { resolveModel } from "../src/adapter";
9
+ import { MODELS, DEFAULT_MODEL, DEFAULT_MODEL_ID, isOllamaModel, getModelsByProvider, getModelsForUI, supportsThinking, THINKING_MODELS, type ModelId } from "../src/models";
10
+ import { Soul, loadSoul } from "../src/soul";
11
+ import { checkOpenRouterAuth, checkAuth } from "../src/agent";
12
+
13
+ // ── Model Resolution ──────────────────────────────────────────────────
14
+
15
+ describe("Model Resolution", () => {
16
+ test("resolves OpenRouter models", () => {
17
+ const model = resolveModel("openrouter/free");
18
+ expect(model.id).toBe("openrouter/free");
19
+ expect(model.provider).toBeDefined();
20
+ });
21
+
22
+ test("resolves Ollama models with custom base URL", () => {
23
+ const model = resolveModel("ollama/llama3", { ollamaBaseUrl: "http://192.168.1.100:11434/v1" });
24
+ expect(model.id).toBe("llama3");
25
+ expect(model.provider).toBe("ollama");
26
+ expect(model.baseUrl).toBe("http://192.168.1.100:11434/v1");
27
+ });
28
+
29
+ test("resolves Ollama models with default base URL", () => {
30
+ const model = resolveModel("ollama/llama3");
31
+ expect(model.baseUrl).toBe("http://localhost:11434/v1");
32
+ });
33
+
34
+ test("resolves unknown Ollama models generically", () => {
35
+ const model = resolveModel("ollama/my-custom-model");
36
+ expect(model.id).toBe("my-custom-model");
37
+ expect(model.name).toContain("Ollama");
38
+ });
39
+
40
+ test("isOllamaModel correctly identifies Ollama models", () => {
41
+ expect(isOllamaModel("ollama/llama3")).toBe(true);
42
+ expect(isOllamaModel("openrouter/free")).toBe(false);
43
+ });
44
+ });
45
+
46
+ // ── MODELS Registry ─────────────────────────────────────────────────
47
+
48
+ describe("MODELS Registry", () => {
49
+ test("has required models", () => {
50
+ expect(MODELS).toHaveProperty("openrouter/free");
51
+ expect(MODELS).toHaveProperty("google/gemma-4-31b-it:free");
52
+ expect(MODELS).toHaveProperty("deepseek/deepseek-r1:free");
53
+ expect(MODELS).toHaveProperty("anthropic/claude-sonnet-4");
54
+ expect(MODELS).toHaveProperty("ollama/llama3");
55
+ expect(MODELS).toHaveProperty("qwen/qwen3-coder:free");
56
+ });
57
+
58
+ test("DEFAULT_MODEL aliases match", () => {
59
+ expect(DEFAULT_MODEL_ID).toBe("openrouter/free");
60
+ expect(DEFAULT_MODEL).toBe("openrouter/free");
61
+ });
62
+
63
+ test("THINKING_MODELS includes expected entries", () => {
64
+ expect(THINKING_MODELS.has("anthropic/claude-sonnet-4")).toBe(true);
65
+ expect(supportsThinking("deepseek/deepseek-r1:free")).toBe(true);
66
+ });
67
+
68
+ test("getModelsForUI strips internal fields", () => {
69
+ const uiModels = getModelsForUI();
70
+ for (const config of Object.values(uiModels)) {
71
+ expect(config).toHaveProperty("name");
72
+ expect(config).toHaveProperty("description");
73
+ expect(config).toHaveProperty("free");
74
+ expect((config as any)._model).toBeUndefined();
75
+ }
76
+ });
77
+
78
+ test("getModelsByProvider returns correct subsets", () => {
79
+ const orModels = getModelsByProvider("openrouter");
80
+ expect(orModels.length).toBeGreaterThan(0);
81
+ orModels.forEach(id => expect((id as string).startsWith("openrouter/")).toBe(true));
82
+
83
+ const ollamaModels = getModelsByProvider("ollama");
84
+ expect(ollamaModels.length).toBeGreaterThan(0);
85
+ });
86
+ });
87
+
88
+ // ── Soul.md ────────────────────────────────────────────────────────
89
+
90
+ describe("Soul.md", () => {
91
+ test("parses basic Soul config from YAML front matter", () => {
92
+ const soulMd = `---
93
+ id: test.soul
94
+ name: TestBot
95
+ version: 1
96
+ identity:
97
+ archetype: assistant
98
+ ---
99
+
100
+ # TestBot
101
+
102
+ You are a helpful assistant.
103
+ `;
104
+ const soul = loadSoul(soulMd);
105
+ expect(soul.config.id).toBe("test.soul");
106
+ expect(soul.config.name).toBe("TestBot");
107
+ expect(soul.config.identity?.archetype).toBe("assistant");
108
+ const prompt = soul.buildPrompt();
109
+ expect(prompt).toContain("TestBot");
110
+ expect(prompt).toContain("helpful assistant");
111
+ });
112
+
113
+ test("selectProfile returns modified Soul with profile overrides", () => {
114
+ const soulMd = `---
115
+ id: test.soul
116
+ version: 1
117
+ name: TestBot
118
+ identity:
119
+ archetype: assistant
120
+ profiles:
121
+ - default
122
+ - concise
123
+ profile_overrides:
124
+ concise:
125
+ voice:
126
+ verbosity: 20
127
+ instructions: Be extremely concise.
128
+ ---
129
+
130
+ # TestBot
131
+
132
+ You are a helpful assistant.
133
+ `;
134
+ const soul = loadSoul(soulMd);
135
+ const concise = soul.selectProfile("concise");
136
+ const prompt = concise.buildPrompt();
137
+ expect(prompt).toContain("extremely concise");
138
+ });
139
+ });
140
+
141
+ // ── Auth Check ─────────────────────────────────────────────────────
142
+
143
+ describe("Auth Check", () => {
144
+ test("checkOpenRouterAuth returns authenticated when key provided", async () => {
145
+ const result = await checkOpenRouterAuth(async () => "sk-test-key");
146
+ expect(result.authenticated).toBe(true);
147
+ expect(result.apiKey).toBe("sk-test-key");
148
+ expect(result.provider).toBe("openrouter");
149
+ });
150
+
151
+ test("checkOpenRouterAuth returns error when no key", async () => {
152
+ const result = await checkOpenRouterAuth(async () => null);
153
+ expect(result.authenticated).toBe(false);
154
+ expect(result.error).toBeDefined();
155
+ });
156
+
157
+ test("checkAuth with Ollama provider returns authenticated", async () => {
158
+ const result = await checkAuth(async (provider) => {
159
+ if (provider === "ollama") return "ollama";
160
+ return undefined;
161
+ });
162
+ expect(result.authenticated).toBe(true);
163
+ expect(result.provider).toBe("ollama");
164
+ });
165
+
166
+ test("checkAuth returns error when no provider has key", async () => {
167
+ const result = await checkAuth(async () => undefined);
168
+ expect(result.authenticated).toBe(false);
169
+ expect(result.error).toBeDefined();
170
+ });
171
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "outDir": "./dist",
14
+ "rootDir": "./src"
15
+ },
16
+ "include": ["src"]
17
+ }