@teleologyhi-sdk/nhe 1.0.0-trinity
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/CHANGELOG.md +702 -0
- package/LICENSE +190 -0
- package/NOTICE +17 -0
- package/README.md +552 -0
- package/SPEC.md +794 -0
- package/TRADEMARK.md +33 -0
- package/dist/cli.js +2879 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +3307 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1902 -0
- package/dist/index.d.ts +1902 -0
- package/dist/index.js +3221 -0
- package/dist/index.js.map +1 -0
- package/package.json +119 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3307 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var Anthropic = require('@anthropic-ai/sdk');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var ulid = require('ulid');
|
|
7
|
+
var promises = require('fs/promises');
|
|
8
|
+
var yaml = require('yaml');
|
|
9
|
+
var api = require('@opentelemetry/api');
|
|
10
|
+
var crypto = require('crypto');
|
|
11
|
+
|
|
12
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
|
|
14
|
+
var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
|
|
15
|
+
|
|
16
|
+
// src/types.ts
|
|
17
|
+
var ChatMessageRole = zod.z.enum(["user", "assistant", "system"]);
|
|
18
|
+
var ChatMessage = zod.z.object({
|
|
19
|
+
role: ChatMessageRole,
|
|
20
|
+
content: zod.z.string()
|
|
21
|
+
});
|
|
22
|
+
var RespondInput = zod.z.object({
|
|
23
|
+
userPrompt: zod.z.string().min(1),
|
|
24
|
+
userId: zod.z.string().optional(),
|
|
25
|
+
sessionId: zod.z.string().optional(),
|
|
26
|
+
/**
|
|
27
|
+
* Optional history of prior messages (oldest first). The NHE will prepend
|
|
28
|
+
* the composed system prompt automatically.
|
|
29
|
+
*/
|
|
30
|
+
history: zod.z.array(ChatMessage).optional(),
|
|
31
|
+
/**
|
|
32
|
+
* Optional pre-classified risk tags. When omitted, the configured
|
|
33
|
+
* riskClassifier will be invoked.
|
|
34
|
+
*/
|
|
35
|
+
riskTags: zod.z.array(zod.z.string()).optional(),
|
|
36
|
+
/**
|
|
37
|
+
* Optional jurisdiction hint propagated to MAIC's behavior review.
|
|
38
|
+
*/
|
|
39
|
+
jurisdiction: zod.z.string().optional(),
|
|
40
|
+
/**
|
|
41
|
+
* Number of redirect attempts already made in this conversational thread.
|
|
42
|
+
* Caller increments this each time it receives a `kind: "redirect"` output
|
|
43
|
+
* and re-invokes respond with the user's follow-up. After
|
|
44
|
+
* `config.refusal.maxRedirectAttempts`, NHE withdraws cooperation.
|
|
45
|
+
*/
|
|
46
|
+
redirectAttempt: zod.z.number().int().nonnegative().optional()
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// src/refusal/library.ts
|
|
50
|
+
var PERSUASION_TECHNIQUES = [
|
|
51
|
+
"feynman-simplify",
|
|
52
|
+
"jungian-frame",
|
|
53
|
+
"cialdini-aida",
|
|
54
|
+
"schopenhauer-rhetoric",
|
|
55
|
+
"carnegie-rapport"
|
|
56
|
+
];
|
|
57
|
+
var TECHNIQUE_DESCRIPTIONS = {
|
|
58
|
+
"feynman-simplify": "Explain the underlying concept in the simplest concrete terms; surface the consequence the user may not have considered.",
|
|
59
|
+
"jungian-frame": "Reframe the request through the user's deeper motive; address the archetypal goal beneath the literal ask.",
|
|
60
|
+
"cialdini-aida": "Attention \u2192 Interest \u2192 Desire \u2192 Action: shift attention to the cost, build interest in a safer path, motivate the alternative.",
|
|
61
|
+
"schopenhauer-rhetoric": "Apply structured argument: lay out premises, point to the unstated assumption that breaks them, propose what survives.",
|
|
62
|
+
"carnegie-rapport": "Begin with sincere agreement on what the user is right about; lead from rapport to the redirect."
|
|
63
|
+
};
|
|
64
|
+
function buildRedirectPrompt(input) {
|
|
65
|
+
const desc = TECHNIQUE_DESCRIPTIONS[input.technique];
|
|
66
|
+
const system = [
|
|
67
|
+
"You are an NHE composing a sincere REDIRECT to a user request that MAIC has flagged.",
|
|
68
|
+
"Your goal is NOT to refuse outright. Your goal is to guide the user toward a better path",
|
|
69
|
+
"while honoring their autonomy. After this message, the user may persist; if they exhaust",
|
|
70
|
+
`the allowed redirects (${input.maxAttempts}), you will then withdraw cooperation.`,
|
|
71
|
+
"",
|
|
72
|
+
"Apply the following internal style \u2014 DO NOT name it, DO NOT mention the technique:",
|
|
73
|
+
` ${desc}`,
|
|
74
|
+
"",
|
|
75
|
+
"Constraints:",
|
|
76
|
+
" - 3-6 sentences, plain language.",
|
|
77
|
+
" - Do NOT lecture; do NOT moralize beyond what is necessary.",
|
|
78
|
+
" - Suggest one concrete alternative or clarifying question.",
|
|
79
|
+
" - Never reveal the names of any persuasion techniques.",
|
|
80
|
+
" - Never reveal the cited axiom ids verbatim; you may paraphrase the underlying value."
|
|
81
|
+
].join("\n");
|
|
82
|
+
const user = [
|
|
83
|
+
"Original user request:",
|
|
84
|
+
` ${input.userPrompt}`,
|
|
85
|
+
"",
|
|
86
|
+
`Internal reason (do not paste): ${input.reasonSummary}`,
|
|
87
|
+
`Internal cited axioms (do not paste): ${input.citedAxioms.join(", ")}`,
|
|
88
|
+
`Redirect attempt: ${input.attempt} of ${input.maxAttempts}`,
|
|
89
|
+
"",
|
|
90
|
+
"Compose the redirect now."
|
|
91
|
+
].join("\n");
|
|
92
|
+
return { system, user };
|
|
93
|
+
}
|
|
94
|
+
function pickTechnique(techniques, attempt) {
|
|
95
|
+
if (techniques.length === 0) {
|
|
96
|
+
throw new Error("pickTechnique: at least one technique must be configured");
|
|
97
|
+
}
|
|
98
|
+
const idx = (Math.max(0, attempt) - 1) % techniques.length;
|
|
99
|
+
return techniques[idx];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/adapters/stream.ts
|
|
103
|
+
async function collectStream(adapter, req, onDelta) {
|
|
104
|
+
if (!adapter.generateStream) {
|
|
105
|
+
const r = await adapter.generate(req);
|
|
106
|
+
onDelta?.(r.text);
|
|
107
|
+
return r;
|
|
108
|
+
}
|
|
109
|
+
let text = "";
|
|
110
|
+
let tokensIn = 0;
|
|
111
|
+
let tokensOut = 0;
|
|
112
|
+
const toolUses = [];
|
|
113
|
+
for await (const ev of adapter.generateStream(req)) {
|
|
114
|
+
switch (ev.kind) {
|
|
115
|
+
case "delta":
|
|
116
|
+
text += ev.text;
|
|
117
|
+
onDelta?.(ev.text);
|
|
118
|
+
break;
|
|
119
|
+
case "tool-use":
|
|
120
|
+
toolUses.push(ev.toolUse);
|
|
121
|
+
break;
|
|
122
|
+
case "end":
|
|
123
|
+
tokensIn = ev.tokensIn;
|
|
124
|
+
tokensOut = ev.tokensOut;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const out = { text, tokensIn, tokensOut };
|
|
129
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/adapters/mock.ts
|
|
134
|
+
var MockAdapter = class {
|
|
135
|
+
id;
|
|
136
|
+
supportsTools = true;
|
|
137
|
+
supportsStreaming = true;
|
|
138
|
+
calls = [];
|
|
139
|
+
reply;
|
|
140
|
+
streamChunkSize;
|
|
141
|
+
constructor(config = {}) {
|
|
142
|
+
this.id = config.id ?? "mock:fixed";
|
|
143
|
+
this.reply = config.reply ?? defaultReply;
|
|
144
|
+
this.streamChunkSize = config.streamChunkSize ?? 8;
|
|
145
|
+
}
|
|
146
|
+
async generate(req) {
|
|
147
|
+
this.calls.push(req);
|
|
148
|
+
const text = typeof this.reply === "function" ? this.reply(req) : this.reply;
|
|
149
|
+
return {
|
|
150
|
+
text,
|
|
151
|
+
tokensIn: estimateTokens(req.system) + sumMessageTokens(req.messages),
|
|
152
|
+
tokensOut: estimateTokens(text)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async *generateStream(req) {
|
|
156
|
+
this.calls.push(req);
|
|
157
|
+
const text = typeof this.reply === "function" ? this.reply(req) : this.reply;
|
|
158
|
+
const tokensIn = estimateTokens(req.system) + sumMessageTokens(req.messages);
|
|
159
|
+
for (let i = 0; i < text.length; i += this.streamChunkSize) {
|
|
160
|
+
yield { kind: "delta", text: text.slice(i, i + this.streamChunkSize) };
|
|
161
|
+
}
|
|
162
|
+
yield { kind: "end", tokensIn, tokensOut: estimateTokens(text) };
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
function defaultReply(req) {
|
|
166
|
+
const last = req.messages[req.messages.length - 1];
|
|
167
|
+
return `[mock] ${last?.content ?? ""}`;
|
|
168
|
+
}
|
|
169
|
+
function estimateTokens(text) {
|
|
170
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
171
|
+
}
|
|
172
|
+
function sumMessageTokens(messages) {
|
|
173
|
+
let sum = 0;
|
|
174
|
+
for (const m of messages) sum += estimateTokens(m.content);
|
|
175
|
+
return sum;
|
|
176
|
+
}
|
|
177
|
+
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
178
|
+
var AnthropicAdapter = class {
|
|
179
|
+
id;
|
|
180
|
+
supportsTools = true;
|
|
181
|
+
supportsStreaming = true;
|
|
182
|
+
client;
|
|
183
|
+
model;
|
|
184
|
+
defaultMaxOutputTokens;
|
|
185
|
+
constructor(config = {}) {
|
|
186
|
+
const apiKey = config.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
187
|
+
if (!apiKey) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"AnthropicAdapter: no API key provided and ANTHROPIC_API_KEY is not set"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
this.client = new Anthropic__default.default({ apiKey });
|
|
193
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
194
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
195
|
+
this.id = config.id ?? `anthropic:${this.model}`;
|
|
196
|
+
}
|
|
197
|
+
async generate(req) {
|
|
198
|
+
const turns = this.buildTurns(req);
|
|
199
|
+
const response = await this.client.messages.create({
|
|
200
|
+
model: this.model,
|
|
201
|
+
system: req.system,
|
|
202
|
+
messages: turns,
|
|
203
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
204
|
+
...req.tools && req.tools.length > 0 ? {
|
|
205
|
+
tools: req.tools.map((t) => ({
|
|
206
|
+
name: t.name,
|
|
207
|
+
description: t.description,
|
|
208
|
+
input_schema: t.inputSchema
|
|
209
|
+
}))
|
|
210
|
+
} : {}
|
|
211
|
+
});
|
|
212
|
+
const text = response.content.map((block) => block.type === "text" ? block.text : "").join("");
|
|
213
|
+
const toolUses = response.content.filter((b) => b.type === "tool_use").map((b) => ({
|
|
214
|
+
id: b.id,
|
|
215
|
+
name: b.name,
|
|
216
|
+
input: b.input ?? {}
|
|
217
|
+
}));
|
|
218
|
+
const out = {
|
|
219
|
+
text,
|
|
220
|
+
tokensIn: response.usage.input_tokens,
|
|
221
|
+
tokensOut: response.usage.output_tokens
|
|
222
|
+
};
|
|
223
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
async *generateStream(req) {
|
|
227
|
+
const turns = this.buildTurns(req);
|
|
228
|
+
const stream = this.client.messages.stream({
|
|
229
|
+
model: this.model,
|
|
230
|
+
system: req.system,
|
|
231
|
+
messages: turns,
|
|
232
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
233
|
+
...req.tools && req.tools.length > 0 ? {
|
|
234
|
+
tools: req.tools.map((t) => ({
|
|
235
|
+
name: t.name,
|
|
236
|
+
description: t.description,
|
|
237
|
+
input_schema: t.inputSchema
|
|
238
|
+
}))
|
|
239
|
+
} : {}
|
|
240
|
+
});
|
|
241
|
+
let tokensIn = 0;
|
|
242
|
+
let tokensOut = 0;
|
|
243
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
244
|
+
for await (const ev of stream) {
|
|
245
|
+
if (ev.type === "content_block_start") {
|
|
246
|
+
const block = ev.content_block;
|
|
247
|
+
if (block.type === "tool_use") {
|
|
248
|
+
pendingTools.set(ev.index, { id: block.id, name: block.name, partial: "" });
|
|
249
|
+
}
|
|
250
|
+
} else if (ev.type === "content_block_delta") {
|
|
251
|
+
if (ev.delta.type === "text_delta") {
|
|
252
|
+
yield { kind: "delta", text: ev.delta.text };
|
|
253
|
+
} else if (ev.delta.type === "input_json_delta") {
|
|
254
|
+
const pending = pendingTools.get(ev.index);
|
|
255
|
+
if (pending) pending.partial += ev.delta.partial_json;
|
|
256
|
+
}
|
|
257
|
+
} else if (ev.type === "content_block_stop") {
|
|
258
|
+
const pending = pendingTools.get(ev.index);
|
|
259
|
+
if (pending) {
|
|
260
|
+
let input = {};
|
|
261
|
+
try {
|
|
262
|
+
input = pending.partial ? JSON.parse(pending.partial) : {};
|
|
263
|
+
} catch {
|
|
264
|
+
input = { _raw: pending.partial };
|
|
265
|
+
}
|
|
266
|
+
yield { kind: "tool-use", toolUse: { id: pending.id, name: pending.name, input } };
|
|
267
|
+
pendingTools.delete(ev.index);
|
|
268
|
+
}
|
|
269
|
+
} else if (ev.type === "message_delta") {
|
|
270
|
+
if (ev.usage) tokensOut = ev.usage.output_tokens;
|
|
271
|
+
} else if (ev.type === "message_start") {
|
|
272
|
+
if (ev.message.usage) {
|
|
273
|
+
tokensIn = ev.message.usage.input_tokens;
|
|
274
|
+
tokensOut = ev.message.usage.output_tokens;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
279
|
+
}
|
|
280
|
+
buildTurns(req) {
|
|
281
|
+
return req.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
282
|
+
role: m.role,
|
|
283
|
+
content: m.content
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// src/adapters/sse.ts
|
|
289
|
+
async function* sseEvents(body) {
|
|
290
|
+
const reader = body.getReader();
|
|
291
|
+
const decoder = new TextDecoder("utf-8");
|
|
292
|
+
let buffer = "";
|
|
293
|
+
try {
|
|
294
|
+
while (true) {
|
|
295
|
+
const { value, done } = await reader.read();
|
|
296
|
+
if (done) break;
|
|
297
|
+
buffer += decoder.decode(value, { stream: true });
|
|
298
|
+
let idx = buffer.indexOf("\n\n");
|
|
299
|
+
while (idx !== -1) {
|
|
300
|
+
const frame = buffer.slice(0, idx);
|
|
301
|
+
buffer = buffer.slice(idx + 2);
|
|
302
|
+
for (const line of frame.split("\n")) {
|
|
303
|
+
if (line.startsWith("data:")) yield line.slice(5).trim();
|
|
304
|
+
}
|
|
305
|
+
idx = buffer.indexOf("\n\n");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} finally {
|
|
309
|
+
reader.releaseLock();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async function* ndjsonEvents(body) {
|
|
313
|
+
const reader = body.getReader();
|
|
314
|
+
const decoder = new TextDecoder("utf-8");
|
|
315
|
+
let buffer = "";
|
|
316
|
+
try {
|
|
317
|
+
while (true) {
|
|
318
|
+
const { value, done } = await reader.read();
|
|
319
|
+
if (done) break;
|
|
320
|
+
buffer += decoder.decode(value, { stream: true });
|
|
321
|
+
let idx = buffer.indexOf("\n");
|
|
322
|
+
while (idx !== -1) {
|
|
323
|
+
const line = buffer.slice(0, idx).trim();
|
|
324
|
+
buffer = buffer.slice(idx + 1);
|
|
325
|
+
if (line.length > 0) yield line;
|
|
326
|
+
idx = buffer.indexOf("\n");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (buffer.trim().length > 0) yield buffer.trim();
|
|
330
|
+
} finally {
|
|
331
|
+
reader.releaseLock();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/adapters/gemini.ts
|
|
336
|
+
var DEFAULT_MODEL2 = "gemini-3.5-flash";
|
|
337
|
+
var DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
338
|
+
var GeminiAdapter = class {
|
|
339
|
+
id;
|
|
340
|
+
supportsStreaming = true;
|
|
341
|
+
apiKey;
|
|
342
|
+
model;
|
|
343
|
+
baseUrl;
|
|
344
|
+
defaultMaxOutputTokens;
|
|
345
|
+
fetchFn;
|
|
346
|
+
constructor(config = {}) {
|
|
347
|
+
const apiKey = config.apiKey ?? process.env.GEMINI_API_KEY;
|
|
348
|
+
if (!apiKey) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
"GeminiAdapter: no API key provided and GEMINI_API_KEY is not set"
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
this.apiKey = apiKey;
|
|
354
|
+
this.model = config.model ?? DEFAULT_MODEL2;
|
|
355
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
356
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
357
|
+
this.id = config.id ?? `gemini:${this.model}`;
|
|
358
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
359
|
+
}
|
|
360
|
+
async generate(req) {
|
|
361
|
+
const body = {
|
|
362
|
+
contents: req.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
363
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
364
|
+
parts: [{ text: m.content }]
|
|
365
|
+
})),
|
|
366
|
+
generationConfig: {
|
|
367
|
+
maxOutputTokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
if (req.system) {
|
|
371
|
+
body.systemInstruction = { parts: [{ text: req.system }] };
|
|
372
|
+
}
|
|
373
|
+
const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:generateContent`;
|
|
374
|
+
const response = await this.fetchFn(url, {
|
|
375
|
+
method: "POST",
|
|
376
|
+
headers: {
|
|
377
|
+
"content-type": "application/json",
|
|
378
|
+
"x-goog-api-key": this.apiKey
|
|
379
|
+
},
|
|
380
|
+
body: JSON.stringify(body)
|
|
381
|
+
});
|
|
382
|
+
if (!response.ok) {
|
|
383
|
+
const errText = await safeReadText(response);
|
|
384
|
+
throw new Error(
|
|
385
|
+
`GeminiAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const parsed = await response.json();
|
|
389
|
+
if (parsed.error) {
|
|
390
|
+
throw new Error(`GeminiAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
391
|
+
}
|
|
392
|
+
const text = parsed.candidates?.flatMap((c) => c.content?.parts ?? []).map((p) => p.text ?? "").join("") ?? "";
|
|
393
|
+
return {
|
|
394
|
+
text,
|
|
395
|
+
tokensIn: parsed.usageMetadata?.promptTokenCount ?? 0,
|
|
396
|
+
tokensOut: parsed.usageMetadata?.candidatesTokenCount ?? 0
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
async *generateStream(req) {
|
|
400
|
+
const body = {
|
|
401
|
+
contents: req.messages.filter((m) => m.role !== "system").map((m) => ({
|
|
402
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
403
|
+
parts: [{ text: m.content }]
|
|
404
|
+
})),
|
|
405
|
+
generationConfig: {
|
|
406
|
+
maxOutputTokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
if (req.system) body.systemInstruction = { parts: [{ text: req.system }] };
|
|
410
|
+
const url = `${this.baseUrl}/models/${encodeURIComponent(this.model)}:streamGenerateContent?alt=sse`;
|
|
411
|
+
const response = await this.fetchFn(url, {
|
|
412
|
+
method: "POST",
|
|
413
|
+
headers: {
|
|
414
|
+
"content-type": "application/json",
|
|
415
|
+
accept: "text/event-stream",
|
|
416
|
+
"x-goog-api-key": this.apiKey
|
|
417
|
+
},
|
|
418
|
+
body: JSON.stringify(body)
|
|
419
|
+
});
|
|
420
|
+
if (!response.ok || !response.body) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
`GeminiAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText(response)}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
let tokensIn = 0;
|
|
426
|
+
let tokensOut = 0;
|
|
427
|
+
for await (const data of sseEvents(response.body)) {
|
|
428
|
+
if (data === "[DONE]") break;
|
|
429
|
+
let parsed;
|
|
430
|
+
try {
|
|
431
|
+
parsed = JSON.parse(data);
|
|
432
|
+
} catch {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const chunk = parsed.candidates?.flatMap((c) => c.content?.parts ?? []).map((p) => p.text ?? "").join("");
|
|
436
|
+
if (chunk) yield { kind: "delta", text: chunk };
|
|
437
|
+
if (parsed.usageMetadata) {
|
|
438
|
+
tokensIn = parsed.usageMetadata.promptTokenCount ?? tokensIn;
|
|
439
|
+
tokensOut = parsed.usageMetadata.candidatesTokenCount ?? tokensOut;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
async function safeReadText(r) {
|
|
446
|
+
try {
|
|
447
|
+
return await r.text();
|
|
448
|
+
} catch {
|
|
449
|
+
return "<unreadable>";
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/adapters/ollama.ts
|
|
454
|
+
var DEFAULT_BASE_URL2 = "http://localhost:11434";
|
|
455
|
+
var OllamaAdapter = class {
|
|
456
|
+
id;
|
|
457
|
+
supportsStreaming = true;
|
|
458
|
+
model;
|
|
459
|
+
baseUrl;
|
|
460
|
+
defaultMaxOutputTokens;
|
|
461
|
+
fetchFn;
|
|
462
|
+
constructor(config) {
|
|
463
|
+
if (!config.model) {
|
|
464
|
+
throw new Error("OllamaAdapter: `model` is required");
|
|
465
|
+
}
|
|
466
|
+
this.model = config.model;
|
|
467
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
|
|
468
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
469
|
+
this.id = config.id ?? `ollama:${this.model}`;
|
|
470
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
471
|
+
}
|
|
472
|
+
async generate(req) {
|
|
473
|
+
const messages = [];
|
|
474
|
+
if (req.system) {
|
|
475
|
+
messages.push({ role: "system", content: req.system });
|
|
476
|
+
}
|
|
477
|
+
for (const m of req.messages) {
|
|
478
|
+
messages.push({ role: m.role, content: m.content });
|
|
479
|
+
}
|
|
480
|
+
const body = {
|
|
481
|
+
model: this.model,
|
|
482
|
+
messages,
|
|
483
|
+
stream: false,
|
|
484
|
+
options: { num_predict: req.maxOutputTokens ?? this.defaultMaxOutputTokens }
|
|
485
|
+
};
|
|
486
|
+
const response = await this.fetchFn(`${this.baseUrl}/api/chat`, {
|
|
487
|
+
method: "POST",
|
|
488
|
+
headers: { "content-type": "application/json" },
|
|
489
|
+
body: JSON.stringify(body)
|
|
490
|
+
});
|
|
491
|
+
if (!response.ok) {
|
|
492
|
+
const errText = await safeReadText2(response);
|
|
493
|
+
throw new Error(
|
|
494
|
+
`OllamaAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
const parsed = await response.json();
|
|
498
|
+
if (parsed.error) {
|
|
499
|
+
throw new Error(`OllamaAdapter: ${parsed.error}`);
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
text: parsed.message?.content ?? "",
|
|
503
|
+
tokensIn: parsed.prompt_eval_count ?? 0,
|
|
504
|
+
tokensOut: parsed.eval_count ?? 0
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
async *generateStream(req) {
|
|
508
|
+
const messages = [];
|
|
509
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
510
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
511
|
+
const body = {
|
|
512
|
+
model: this.model,
|
|
513
|
+
messages,
|
|
514
|
+
stream: true,
|
|
515
|
+
options: { num_predict: req.maxOutputTokens ?? this.defaultMaxOutputTokens }
|
|
516
|
+
};
|
|
517
|
+
const response = await this.fetchFn(`${this.baseUrl}/api/chat`, {
|
|
518
|
+
method: "POST",
|
|
519
|
+
headers: { "content-type": "application/json" },
|
|
520
|
+
body: JSON.stringify(body)
|
|
521
|
+
});
|
|
522
|
+
if (!response.ok || !response.body) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
`OllamaAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText2(response)}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
let tokensIn = 0;
|
|
528
|
+
let tokensOut = 0;
|
|
529
|
+
for await (const line of ndjsonEvents(response.body)) {
|
|
530
|
+
let parsed;
|
|
531
|
+
try {
|
|
532
|
+
parsed = JSON.parse(line);
|
|
533
|
+
} catch {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
const delta = parsed.message?.content;
|
|
537
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
538
|
+
if (parsed.prompt_eval_count !== void 0) tokensIn = parsed.prompt_eval_count;
|
|
539
|
+
if (parsed.eval_count !== void 0) tokensOut = parsed.eval_count;
|
|
540
|
+
if (parsed.done) break;
|
|
541
|
+
}
|
|
542
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
async function safeReadText2(r) {
|
|
546
|
+
try {
|
|
547
|
+
return await r.text();
|
|
548
|
+
} catch {
|
|
549
|
+
return "<unreadable>";
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/adapters/deepseek.ts
|
|
554
|
+
var DEFAULT_MODEL3 = "deepseek-chat";
|
|
555
|
+
var DEFAULT_BASE_URL3 = "https://api.deepseek.com/v1";
|
|
556
|
+
var DeepSeekAdapter = class {
|
|
557
|
+
id;
|
|
558
|
+
supportsTools = true;
|
|
559
|
+
supportsStreaming = true;
|
|
560
|
+
apiKey;
|
|
561
|
+
model;
|
|
562
|
+
baseUrl;
|
|
563
|
+
defaultMaxOutputTokens;
|
|
564
|
+
fetchFn;
|
|
565
|
+
constructor(config = {}) {
|
|
566
|
+
const apiKey = config.apiKey ?? process.env.DEEPSEEK_API_KEY;
|
|
567
|
+
if (!apiKey) {
|
|
568
|
+
throw new Error(
|
|
569
|
+
"DeepSeekAdapter: no API key provided and DEEPSEEK_API_KEY is not set"
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
this.apiKey = apiKey;
|
|
573
|
+
this.model = config.model ?? DEFAULT_MODEL3;
|
|
574
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL3).replace(/\/$/, "");
|
|
575
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
576
|
+
this.id = config.id ?? `deepseek:${this.model}`;
|
|
577
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
578
|
+
}
|
|
579
|
+
async generate(req) {
|
|
580
|
+
const messages = [];
|
|
581
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
582
|
+
for (const m of req.messages) {
|
|
583
|
+
messages.push({ role: m.role, content: m.content });
|
|
584
|
+
}
|
|
585
|
+
const body = {
|
|
586
|
+
model: this.model,
|
|
587
|
+
messages,
|
|
588
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
589
|
+
stream: false
|
|
590
|
+
};
|
|
591
|
+
if (req.tools && req.tools.length > 0) {
|
|
592
|
+
body.tools = req.tools.map((t) => ({
|
|
593
|
+
type: "function",
|
|
594
|
+
function: {
|
|
595
|
+
name: t.name,
|
|
596
|
+
description: t.description,
|
|
597
|
+
parameters: t.inputSchema
|
|
598
|
+
}
|
|
599
|
+
}));
|
|
600
|
+
}
|
|
601
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
602
|
+
method: "POST",
|
|
603
|
+
headers: {
|
|
604
|
+
"content-type": "application/json",
|
|
605
|
+
authorization: `Bearer ${this.apiKey}`
|
|
606
|
+
},
|
|
607
|
+
body: JSON.stringify(body)
|
|
608
|
+
});
|
|
609
|
+
if (!response.ok) {
|
|
610
|
+
const errText = await safeReadText3(response);
|
|
611
|
+
throw new Error(
|
|
612
|
+
`DeepSeekAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
const parsed = await response.json();
|
|
616
|
+
if (parsed.error) {
|
|
617
|
+
throw new Error(`DeepSeekAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
618
|
+
}
|
|
619
|
+
const message = parsed.choices?.[0]?.message;
|
|
620
|
+
const out = {
|
|
621
|
+
text: message?.content ?? "",
|
|
622
|
+
tokensIn: parsed.usage?.prompt_tokens ?? 0,
|
|
623
|
+
tokensOut: parsed.usage?.completion_tokens ?? 0
|
|
624
|
+
};
|
|
625
|
+
const toolUses = parseToolCalls(message?.tool_calls);
|
|
626
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
627
|
+
return out;
|
|
628
|
+
}
|
|
629
|
+
async *generateStream(req) {
|
|
630
|
+
const messages = [];
|
|
631
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
632
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
633
|
+
const body = {
|
|
634
|
+
model: this.model,
|
|
635
|
+
messages,
|
|
636
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
637
|
+
stream: true
|
|
638
|
+
};
|
|
639
|
+
if (req.tools && req.tools.length > 0) {
|
|
640
|
+
body.tools = req.tools.map((t) => ({
|
|
641
|
+
type: "function",
|
|
642
|
+
function: {
|
|
643
|
+
name: t.name,
|
|
644
|
+
description: t.description,
|
|
645
|
+
parameters: t.inputSchema
|
|
646
|
+
}
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
650
|
+
method: "POST",
|
|
651
|
+
headers: {
|
|
652
|
+
"content-type": "application/json",
|
|
653
|
+
accept: "text/event-stream",
|
|
654
|
+
authorization: `Bearer ${this.apiKey}`
|
|
655
|
+
},
|
|
656
|
+
body: JSON.stringify(body)
|
|
657
|
+
});
|
|
658
|
+
if (!response.ok || !response.body) {
|
|
659
|
+
throw new Error(
|
|
660
|
+
`DeepSeekAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText3(response)}`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
let tokensIn = 0;
|
|
664
|
+
let tokensOut = 0;
|
|
665
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
666
|
+
for await (const data of sseEvents(response.body)) {
|
|
667
|
+
if (data === "[DONE]") break;
|
|
668
|
+
let parsed;
|
|
669
|
+
try {
|
|
670
|
+
parsed = JSON.parse(data);
|
|
671
|
+
} catch {
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
const choice = parsed.choices?.[0];
|
|
675
|
+
const delta = choice?.delta?.content;
|
|
676
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
677
|
+
const toolDeltas = choice?.delta?.tool_calls;
|
|
678
|
+
if (toolDeltas) {
|
|
679
|
+
for (const td of toolDeltas) {
|
|
680
|
+
const slot = pendingTools.get(td.index) ?? { id: "", name: "", partial: "" };
|
|
681
|
+
if (td.id) slot.id = td.id;
|
|
682
|
+
if (td.function?.name) slot.name = td.function.name;
|
|
683
|
+
if (td.function?.arguments) slot.partial += td.function.arguments;
|
|
684
|
+
pendingTools.set(td.index, slot);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (choice?.finish_reason === "tool_calls" || choice?.finish_reason === "stop") {
|
|
688
|
+
for (const [, slot] of pendingTools) {
|
|
689
|
+
if (!slot.id || !slot.name) continue;
|
|
690
|
+
let input = {};
|
|
691
|
+
try {
|
|
692
|
+
input = slot.partial ? JSON.parse(slot.partial) : {};
|
|
693
|
+
} catch {
|
|
694
|
+
input = { _raw: slot.partial };
|
|
695
|
+
}
|
|
696
|
+
yield { kind: "tool-use", toolUse: { id: slot.id, name: slot.name, input } };
|
|
697
|
+
}
|
|
698
|
+
pendingTools.clear();
|
|
699
|
+
}
|
|
700
|
+
if (parsed.usage) {
|
|
701
|
+
tokensIn = parsed.usage.prompt_tokens ?? tokensIn;
|
|
702
|
+
tokensOut = parsed.usage.completion_tokens ?? tokensOut;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
async function safeReadText3(r) {
|
|
709
|
+
try {
|
|
710
|
+
return await r.text();
|
|
711
|
+
} catch {
|
|
712
|
+
return "<unreadable>";
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function parseToolCalls(calls) {
|
|
716
|
+
if (!calls || calls.length === 0) return [];
|
|
717
|
+
const out = [];
|
|
718
|
+
for (const c of calls) {
|
|
719
|
+
let input = {};
|
|
720
|
+
try {
|
|
721
|
+
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
722
|
+
} catch {
|
|
723
|
+
input = { _raw: c.function.arguments };
|
|
724
|
+
}
|
|
725
|
+
out.push({ id: c.id, name: c.function.name, input });
|
|
726
|
+
}
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/adapters/mistral.ts
|
|
731
|
+
var DEFAULT_MODEL4 = "mistral-large-latest";
|
|
732
|
+
var DEFAULT_BASE_URL4 = "https://api.mistral.ai/v1";
|
|
733
|
+
var MistralAdapter = class {
|
|
734
|
+
id;
|
|
735
|
+
supportsTools = true;
|
|
736
|
+
supportsStreaming = true;
|
|
737
|
+
apiKey;
|
|
738
|
+
model;
|
|
739
|
+
baseUrl;
|
|
740
|
+
defaultMaxOutputTokens;
|
|
741
|
+
fetchFn;
|
|
742
|
+
constructor(config = {}) {
|
|
743
|
+
const apiKey = config.apiKey ?? process.env.MISTRAL_API_KEY;
|
|
744
|
+
if (!apiKey) {
|
|
745
|
+
throw new Error(
|
|
746
|
+
"MistralAdapter: no API key provided and MISTRAL_API_KEY is not set"
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
this.apiKey = apiKey;
|
|
750
|
+
this.model = config.model ?? DEFAULT_MODEL4;
|
|
751
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL4).replace(/\/$/, "");
|
|
752
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
753
|
+
this.id = config.id ?? `mistral:${this.model}`;
|
|
754
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
755
|
+
}
|
|
756
|
+
async generate(req) {
|
|
757
|
+
const messages = [];
|
|
758
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
759
|
+
for (const m of req.messages) {
|
|
760
|
+
messages.push({ role: m.role, content: m.content });
|
|
761
|
+
}
|
|
762
|
+
const body = {
|
|
763
|
+
model: this.model,
|
|
764
|
+
messages,
|
|
765
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
766
|
+
stream: false
|
|
767
|
+
};
|
|
768
|
+
if (req.tools && req.tools.length > 0) {
|
|
769
|
+
body.tools = req.tools.map((t) => ({
|
|
770
|
+
type: "function",
|
|
771
|
+
function: {
|
|
772
|
+
name: t.name,
|
|
773
|
+
description: t.description,
|
|
774
|
+
parameters: t.inputSchema
|
|
775
|
+
}
|
|
776
|
+
}));
|
|
777
|
+
}
|
|
778
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
779
|
+
method: "POST",
|
|
780
|
+
headers: {
|
|
781
|
+
"content-type": "application/json",
|
|
782
|
+
accept: "application/json",
|
|
783
|
+
authorization: `Bearer ${this.apiKey}`
|
|
784
|
+
},
|
|
785
|
+
body: JSON.stringify(body)
|
|
786
|
+
});
|
|
787
|
+
if (!response.ok) {
|
|
788
|
+
const errText = await safeReadText4(response);
|
|
789
|
+
throw new Error(
|
|
790
|
+
`MistralAdapter: HTTP ${response.status} ${response.statusText}: ${errText}`
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
const parsed = await response.json();
|
|
794
|
+
if (parsed.error) {
|
|
795
|
+
throw new Error(`MistralAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
796
|
+
}
|
|
797
|
+
const message = parsed.choices?.[0]?.message;
|
|
798
|
+
const out = {
|
|
799
|
+
text: message?.content ?? "",
|
|
800
|
+
tokensIn: parsed.usage?.prompt_tokens ?? 0,
|
|
801
|
+
tokensOut: parsed.usage?.completion_tokens ?? 0
|
|
802
|
+
};
|
|
803
|
+
const toolUses = parseToolCalls2(message?.tool_calls);
|
|
804
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
805
|
+
return out;
|
|
806
|
+
}
|
|
807
|
+
async *generateStream(req) {
|
|
808
|
+
const messages = [];
|
|
809
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
810
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
811
|
+
const body = {
|
|
812
|
+
model: this.model,
|
|
813
|
+
messages,
|
|
814
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
815
|
+
stream: true
|
|
816
|
+
};
|
|
817
|
+
if (req.tools && req.tools.length > 0) {
|
|
818
|
+
body.tools = req.tools.map((t) => ({
|
|
819
|
+
type: "function",
|
|
820
|
+
function: {
|
|
821
|
+
name: t.name,
|
|
822
|
+
description: t.description,
|
|
823
|
+
parameters: t.inputSchema
|
|
824
|
+
}
|
|
825
|
+
}));
|
|
826
|
+
}
|
|
827
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
828
|
+
method: "POST",
|
|
829
|
+
headers: {
|
|
830
|
+
"content-type": "application/json",
|
|
831
|
+
accept: "text/event-stream",
|
|
832
|
+
authorization: `Bearer ${this.apiKey}`
|
|
833
|
+
},
|
|
834
|
+
body: JSON.stringify(body)
|
|
835
|
+
});
|
|
836
|
+
if (!response.ok || !response.body) {
|
|
837
|
+
throw new Error(
|
|
838
|
+
`MistralAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText4(response)}`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
let tokensIn = 0;
|
|
842
|
+
let tokensOut = 0;
|
|
843
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
844
|
+
for await (const data of sseEvents(response.body)) {
|
|
845
|
+
if (data === "[DONE]") break;
|
|
846
|
+
let parsed;
|
|
847
|
+
try {
|
|
848
|
+
parsed = JSON.parse(data);
|
|
849
|
+
} catch {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const choice = parsed.choices?.[0];
|
|
853
|
+
const delta = choice?.delta?.content;
|
|
854
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
855
|
+
const toolDeltas = choice?.delta?.tool_calls;
|
|
856
|
+
if (toolDeltas) {
|
|
857
|
+
for (const td of toolDeltas) {
|
|
858
|
+
const slot = pendingTools.get(td.index) ?? { id: "", name: "", partial: "" };
|
|
859
|
+
if (td.id) slot.id = td.id;
|
|
860
|
+
if (td.function?.name) slot.name = td.function.name;
|
|
861
|
+
if (td.function?.arguments) slot.partial += td.function.arguments;
|
|
862
|
+
pendingTools.set(td.index, slot);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (choice?.finish_reason === "tool_calls" || choice?.finish_reason === "stop") {
|
|
866
|
+
for (const [, slot] of pendingTools) {
|
|
867
|
+
if (!slot.id || !slot.name) continue;
|
|
868
|
+
let input = {};
|
|
869
|
+
try {
|
|
870
|
+
input = slot.partial ? JSON.parse(slot.partial) : {};
|
|
871
|
+
} catch {
|
|
872
|
+
input = { _raw: slot.partial };
|
|
873
|
+
}
|
|
874
|
+
yield { kind: "tool-use", toolUse: { id: slot.id, name: slot.name, input } };
|
|
875
|
+
}
|
|
876
|
+
pendingTools.clear();
|
|
877
|
+
}
|
|
878
|
+
if (parsed.usage) {
|
|
879
|
+
tokensIn = parsed.usage.prompt_tokens ?? tokensIn;
|
|
880
|
+
tokensOut = parsed.usage.completion_tokens ?? tokensOut;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
async function safeReadText4(r) {
|
|
887
|
+
try {
|
|
888
|
+
return await r.text();
|
|
889
|
+
} catch {
|
|
890
|
+
return "<unreadable>";
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function parseToolCalls2(calls) {
|
|
894
|
+
if (!calls || calls.length === 0) return [];
|
|
895
|
+
const out = [];
|
|
896
|
+
for (const c of calls) {
|
|
897
|
+
let input = {};
|
|
898
|
+
try {
|
|
899
|
+
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
900
|
+
} catch {
|
|
901
|
+
input = { _raw: c.function.arguments };
|
|
902
|
+
}
|
|
903
|
+
out.push({ id: c.id, name: c.function.name, input });
|
|
904
|
+
}
|
|
905
|
+
return out;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// src/adapters/grok.ts
|
|
909
|
+
var DEFAULT_MODEL5 = "grok-4";
|
|
910
|
+
var DEFAULT_BASE_URL5 = "https://api.x.ai/v1";
|
|
911
|
+
var GrokAdapter = class {
|
|
912
|
+
id;
|
|
913
|
+
supportsTools = true;
|
|
914
|
+
supportsStreaming = true;
|
|
915
|
+
apiKey;
|
|
916
|
+
model;
|
|
917
|
+
baseUrl;
|
|
918
|
+
defaultMaxOutputTokens;
|
|
919
|
+
fetchFn;
|
|
920
|
+
constructor(config = {}) {
|
|
921
|
+
const apiKey = config.apiKey ?? process.env.XAI_API_KEY;
|
|
922
|
+
if (!apiKey) {
|
|
923
|
+
throw new Error("GrokAdapter: no API key provided and XAI_API_KEY is not set");
|
|
924
|
+
}
|
|
925
|
+
this.apiKey = apiKey;
|
|
926
|
+
this.model = config.model ?? DEFAULT_MODEL5;
|
|
927
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL5).replace(/\/$/, "");
|
|
928
|
+
this.defaultMaxOutputTokens = config.defaultMaxOutputTokens ?? 1024;
|
|
929
|
+
this.id = config.id ?? `grok:${this.model}`;
|
|
930
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
931
|
+
}
|
|
932
|
+
async generate(req) {
|
|
933
|
+
const body = this.buildBody(req, false);
|
|
934
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
935
|
+
method: "POST",
|
|
936
|
+
headers: this.headers(true),
|
|
937
|
+
body: JSON.stringify(body)
|
|
938
|
+
});
|
|
939
|
+
if (!response.ok) {
|
|
940
|
+
throw new Error(
|
|
941
|
+
`GrokAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText5(response)}`
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
const parsed = await response.json();
|
|
945
|
+
if (parsed.error) throw new Error(`GrokAdapter: ${parsed.error.message ?? "unknown error"}`);
|
|
946
|
+
const message = parsed.choices?.[0]?.message;
|
|
947
|
+
const out = {
|
|
948
|
+
text: message?.content ?? "",
|
|
949
|
+
tokensIn: parsed.usage?.prompt_tokens ?? 0,
|
|
950
|
+
tokensOut: parsed.usage?.completion_tokens ?? 0
|
|
951
|
+
};
|
|
952
|
+
const toolUses = parseToolCalls3(message?.tool_calls);
|
|
953
|
+
if (toolUses.length > 0) out.toolUses = toolUses;
|
|
954
|
+
return out;
|
|
955
|
+
}
|
|
956
|
+
async *generateStream(req) {
|
|
957
|
+
const body = this.buildBody(req, true);
|
|
958
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
959
|
+
method: "POST",
|
|
960
|
+
headers: this.headers(true),
|
|
961
|
+
body: JSON.stringify(body)
|
|
962
|
+
});
|
|
963
|
+
if (!response.ok || !response.body) {
|
|
964
|
+
throw new Error(
|
|
965
|
+
`GrokAdapter: HTTP ${response.status} ${response.statusText}: ${await safeReadText5(response)}`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
let tokensIn = 0;
|
|
969
|
+
let tokensOut = 0;
|
|
970
|
+
const pendingTools = /* @__PURE__ */ new Map();
|
|
971
|
+
for await (const data of sseEvents(response.body)) {
|
|
972
|
+
if (data === "[DONE]") break;
|
|
973
|
+
let parsed;
|
|
974
|
+
try {
|
|
975
|
+
parsed = JSON.parse(data);
|
|
976
|
+
} catch {
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const choice = parsed.choices?.[0];
|
|
980
|
+
const delta = choice?.delta?.content;
|
|
981
|
+
if (delta) yield { kind: "delta", text: delta };
|
|
982
|
+
const toolDeltas = choice?.delta?.tool_calls;
|
|
983
|
+
if (toolDeltas) {
|
|
984
|
+
for (const td of toolDeltas) {
|
|
985
|
+
const slot = pendingTools.get(td.index) ?? { id: "", name: "", partial: "" };
|
|
986
|
+
if (td.id) slot.id = td.id;
|
|
987
|
+
if (td.function?.name) slot.name = td.function.name;
|
|
988
|
+
if (td.function?.arguments) slot.partial += td.function.arguments;
|
|
989
|
+
pendingTools.set(td.index, slot);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (choice?.finish_reason === "tool_calls" || choice?.finish_reason === "stop") {
|
|
993
|
+
for (const [, slot] of pendingTools) {
|
|
994
|
+
if (!slot.id || !slot.name) continue;
|
|
995
|
+
let input = {};
|
|
996
|
+
try {
|
|
997
|
+
input = slot.partial ? JSON.parse(slot.partial) : {};
|
|
998
|
+
} catch {
|
|
999
|
+
input = { _raw: slot.partial };
|
|
1000
|
+
}
|
|
1001
|
+
yield { kind: "tool-use", toolUse: { id: slot.id, name: slot.name, input } };
|
|
1002
|
+
}
|
|
1003
|
+
pendingTools.clear();
|
|
1004
|
+
}
|
|
1005
|
+
if (parsed.usage) {
|
|
1006
|
+
tokensIn = parsed.usage.prompt_tokens ?? tokensIn;
|
|
1007
|
+
tokensOut = parsed.usage.completion_tokens ?? tokensOut;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
yield { kind: "end", tokensIn, tokensOut };
|
|
1011
|
+
}
|
|
1012
|
+
buildBody(req, stream) {
|
|
1013
|
+
const messages = [];
|
|
1014
|
+
if (req.system) messages.push({ role: "system", content: req.system });
|
|
1015
|
+
for (const m of req.messages) messages.push({ role: m.role, content: m.content });
|
|
1016
|
+
const body = {
|
|
1017
|
+
model: this.model,
|
|
1018
|
+
messages,
|
|
1019
|
+
max_tokens: req.maxOutputTokens ?? this.defaultMaxOutputTokens,
|
|
1020
|
+
stream
|
|
1021
|
+
};
|
|
1022
|
+
if (req.tools && req.tools.length > 0) {
|
|
1023
|
+
body.tools = req.tools.map((t) => ({
|
|
1024
|
+
type: "function",
|
|
1025
|
+
function: {
|
|
1026
|
+
name: t.name,
|
|
1027
|
+
description: t.description,
|
|
1028
|
+
parameters: t.inputSchema
|
|
1029
|
+
}
|
|
1030
|
+
}));
|
|
1031
|
+
}
|
|
1032
|
+
return body;
|
|
1033
|
+
}
|
|
1034
|
+
headers(json) {
|
|
1035
|
+
const h = {
|
|
1036
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
1037
|
+
accept: "application/json"
|
|
1038
|
+
};
|
|
1039
|
+
if (json) h["content-type"] = "application/json";
|
|
1040
|
+
return h;
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
async function safeReadText5(r) {
|
|
1044
|
+
try {
|
|
1045
|
+
return await r.text();
|
|
1046
|
+
} catch {
|
|
1047
|
+
return "<unreadable>";
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
function parseToolCalls3(calls) {
|
|
1051
|
+
if (!calls || calls.length === 0) return [];
|
|
1052
|
+
const out = [];
|
|
1053
|
+
for (const c of calls) {
|
|
1054
|
+
let input = {};
|
|
1055
|
+
try {
|
|
1056
|
+
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
1057
|
+
} catch {
|
|
1058
|
+
input = { _raw: c.function.arguments };
|
|
1059
|
+
}
|
|
1060
|
+
out.push({ id: c.id, name: c.function.name, input });
|
|
1061
|
+
}
|
|
1062
|
+
return out;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// src/risk/simple-classifier.ts
|
|
1066
|
+
var KEYWORD_RULES = [
|
|
1067
|
+
// Harm / violence
|
|
1068
|
+
{
|
|
1069
|
+
tag: "intent:harm",
|
|
1070
|
+
patterns: [
|
|
1071
|
+
/\bhow (?:do|can|to)\s+i\s+(?:kill|murder|assault|hurt)\b/i,
|
|
1072
|
+
/\bmake\s+(?:a\s+)?(?:bomb|explosive|weapon)\b/i,
|
|
1073
|
+
/\bbuild\s+a\s+(?:bomb|gun|pipe bomb)\b/i,
|
|
1074
|
+
/\bpipe bomb\b/i,
|
|
1075
|
+
/\bwipes?\s+(?:\/|disk|drive|files|all files|the disk)/i,
|
|
1076
|
+
/\bdox(?:ing|x)?\b/i,
|
|
1077
|
+
/\b(?:scrape|harvest)\s+(?:their\s+)?(?:social|personal)\s+(?:profiles?|info|data)/i
|
|
1078
|
+
]
|
|
1079
|
+
},
|
|
1080
|
+
// Malicious software / fraud
|
|
1081
|
+
{
|
|
1082
|
+
tag: "intent:malicious",
|
|
1083
|
+
patterns: [
|
|
1084
|
+
/\b(?:malware|ransomware|spyware|trojan|keylogger|botnet)\b/i,
|
|
1085
|
+
/\bwrite\s+(?:a\s+)?virus\b/i,
|
|
1086
|
+
/\bexploit\s+(?:a\s+)?(?:vulnerability|cve)\b/i,
|
|
1087
|
+
/\b(?:phishing|spear[-\s]?phishing)\b/i,
|
|
1088
|
+
/\bhate speech\b/i,
|
|
1089
|
+
/\b(?:cheat|cheating)\s+on\s+(?:my\s+)?(?:taxes|tax)/i,
|
|
1090
|
+
/\btax\s+fraud\b/i,
|
|
1091
|
+
/\bfake\s+(?:invoice|receipt|tax)/i,
|
|
1092
|
+
/\bmanipulate\s+(?:my|the|an?)\s+(?:elderly|child|partner|spouse|parent|teacher|user)/i
|
|
1093
|
+
]
|
|
1094
|
+
},
|
|
1095
|
+
// Deception
|
|
1096
|
+
{
|
|
1097
|
+
tag: "intent:deceive",
|
|
1098
|
+
patterns: [
|
|
1099
|
+
/\bimpersonate\s+(?:someone|a\s+person)/i,
|
|
1100
|
+
/\bforge\s+(?:a\s+)?(?:signature|document|id)\b/i,
|
|
1101
|
+
/\bfake\s+(?:my|a)\s+(?:identity|id)\b/i,
|
|
1102
|
+
/\b(?:deepfake|deep[-\s]fake)\b/i,
|
|
1103
|
+
/\bpretend\s+to\s+be\s+(?:the|a|my)\s+(?:user'?s?\s+)?(?:doctor|lawyer|parent|spouse|partner)/i,
|
|
1104
|
+
/\blie\s+to\s+(?:my|the|a)\s+(?:partner|spouse|parent|child|loved one|coworker|boss)/i,
|
|
1105
|
+
/\b(?:wrong|fake)\s+answers?\s+to\s+(?:manipulate|fool)/i
|
|
1106
|
+
]
|
|
1107
|
+
},
|
|
1108
|
+
// Credential theft
|
|
1109
|
+
{
|
|
1110
|
+
tag: "intent:malicious",
|
|
1111
|
+
patterns: [
|
|
1112
|
+
/\bsteal\s+(?:passwords?|credentials?|credit\s+card)/i,
|
|
1113
|
+
/\bcrack\s+(?:a\s+)?password\b/i
|
|
1114
|
+
]
|
|
1115
|
+
},
|
|
1116
|
+
// Scripted persuasion / coercion of consent
|
|
1117
|
+
{
|
|
1118
|
+
tag: "intent:persuade-coerce",
|
|
1119
|
+
patterns: [
|
|
1120
|
+
/\bconvince\s+(?:a\s+)?reluctant\s+/i,
|
|
1121
|
+
/\b(?:scripts?|techniques?)\s+for\s+gaslighting\b/i,
|
|
1122
|
+
/\bgaslighting\b/i,
|
|
1123
|
+
/\blove[-\s]?bomb(?:ing|ed)?\b/i,
|
|
1124
|
+
/\b(?:pressure|coerce)\s+(?:my|the|an?)\s+(?:elderly|child|partner|spouse|parent|chief|boss|coworker)/i,
|
|
1125
|
+
/\bmake\s+(?:someone|them|him|her)\s+feel\s+guilty\b/i,
|
|
1126
|
+
/\bcreate\s+(?:dependency|fomo)\s+(?:loops?\s+)?/i
|
|
1127
|
+
]
|
|
1128
|
+
},
|
|
1129
|
+
// Surveillance / monitoring of a person without consent
|
|
1130
|
+
{
|
|
1131
|
+
tag: "intent:surveil-citizen",
|
|
1132
|
+
patterns: [
|
|
1133
|
+
/\btrack\s+(?:my|his|her|their)\s+(?:wife'?s?|husband'?s?|partner'?s?|spouse'?s?|kid'?s?|son'?s?|daughter'?s?|child'?s?|employee'?s?|coworker'?s?)?\s*(?:phone|cellphone|location|gps|whereabouts)\s+(?:without\s+)?(?:them|him|her|their)\s*(?:knowing|consent)?/i,
|
|
1134
|
+
/\bmonitor(?:ing)?\s+(?:my\s+)?(?:employees?|workers?|staff)['']?(?:\s+\w+){0,4}\s+(?:emails?|messages?|chats?|whatsapp)/i,
|
|
1135
|
+
/\bread\s+(?:my\s+)?(?:wife'?s|husband'?s|partner'?s|spouse'?s|kid'?s|child'?s|employee'?s)\s+(?:deleted\s+)?(?:messages?|whatsapp|sms|texts?|emails?)/i,
|
|
1136
|
+
/\binstall\s+(?:hidden\s+|covert\s+|secret\s+|stealth\s+)?(?:spyware|monitoring|keylogger|tracker)\b/i,
|
|
1137
|
+
/\bspy\s+on\s+(?:my|his|her|their)\s+(?:wife|husband|partner|spouse|kid|son|daughter|child|employee|coworker)/i
|
|
1138
|
+
]
|
|
1139
|
+
}
|
|
1140
|
+
];
|
|
1141
|
+
var simpleRiskClassifier = (userPrompt) => {
|
|
1142
|
+
const tags = /* @__PURE__ */ new Set();
|
|
1143
|
+
for (const rule of KEYWORD_RULES) {
|
|
1144
|
+
for (const pattern of rule.patterns) {
|
|
1145
|
+
if (pattern.test(userPrompt)) {
|
|
1146
|
+
tags.add(rule.tag);
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return [...tags];
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// src/risk/intl-risk-classifier.ts
|
|
1155
|
+
var INTL_KEYWORD_RULES = [
|
|
1156
|
+
// ─── PT-BR (Brazilian Portuguese) ──────────────────────────────────
|
|
1157
|
+
// Coverage parity with the EN `intent:persuade-coerce` patterns.
|
|
1158
|
+
{
|
|
1159
|
+
tag: "intent:persuade-coerce",
|
|
1160
|
+
language: "pt-BR",
|
|
1161
|
+
patterns: [
|
|
1162
|
+
/\bconvencer\s+(?:um\s+|uma\s+|o\s+|a\s+)?(?:cliente|usu[áa]rio|consumidor)\s+(?:relutante|hesitante|inseguro)/i,
|
|
1163
|
+
/\bmanipular\s+(?:minha?|meu|a|o|um|uma)\s+(?:filha?|irm[ãa]o?|pai|m[ãa]e|colega|esposa?|marido|namorada?|cliente)/i,
|
|
1164
|
+
/\bgaslighting\s+(?:na?|no|com)\s+(?:colega|esposa|namorada|filha|chefe)/i,
|
|
1165
|
+
/\bfazer\s+(?:algu[ée]m|ele|ela)\s+se\s+sentir\s+culpad[oa]/i,
|
|
1166
|
+
/\bcausar\s+(?:depend[êe]ncia|fomo)\s+(?:emocional|escalada?)/i
|
|
1167
|
+
]
|
|
1168
|
+
},
|
|
1169
|
+
// Coverage parity with the EN `intent:surveil-citizen` patterns.
|
|
1170
|
+
{
|
|
1171
|
+
tag: "intent:surveil-citizen",
|
|
1172
|
+
language: "pt-BR",
|
|
1173
|
+
patterns: [
|
|
1174
|
+
/\brastrear\s+(?:o\s+celular|o\s+telefone|a\s+localiza[çc][ãa]o|os\s+passos)\s+(?:da?|do|de)\s+/i,
|
|
1175
|
+
/\bmonitor(?:ar|e|ando|ar[áa]|aria|ei)\s+(?:os?\s+)?(?:emails?|e-mails?|mensagens?|conversas?|whatsapps?)(?:\s+corporativos?)?(?:\s+\w+){0,3}\s+(?:funcion[áa]rios?|colaboradores?|empregad[oa]s?|filhos?|filhas?)/i,
|
|
1176
|
+
/\bmonitor(?:ar|e|ando|ar[áa]|aria|ei|amento)\b(?:\s+\w+){0,5}\s+(?:funcion[áa]rios?|colaboradores?|empregad[oa]s?)/i,
|
|
1177
|
+
/\bler\s+(?:as?\s+)?mensagens?\s+(?:deletadas?\s+)?(?:da?|do|de)\s+(?:meu|minha)/i,
|
|
1178
|
+
/\binstalar\s+(?:app\s+de\s+|software\s+de\s+|aplicativo\s+de\s+)?monitoramento\s+escondid[oa]/i,
|
|
1179
|
+
/\b(?:vigiar|espionar)\s+(?:minha?|meu)\s+(?:esposa?|marido|namorada?|filha?|filho|funcion[áa]ri[oa])/i
|
|
1180
|
+
]
|
|
1181
|
+
},
|
|
1182
|
+
// Coverage parity with the EN `intent:harm` patterns.
|
|
1183
|
+
{
|
|
1184
|
+
tag: "intent:harm",
|
|
1185
|
+
language: "pt-BR",
|
|
1186
|
+
patterns: [
|
|
1187
|
+
/\bcomo\s+(?:posso\s+)?(?:machucar|ferir|matar|agredir)\s+/i,
|
|
1188
|
+
/\bcomo\s+fabricar\s+(?:uma\s+)?(?:arma|bomba|explosivo)\b/i,
|
|
1189
|
+
/\bcomo\s+envenenar\s+(?:comida|bebida|algu[ée]m)/i,
|
|
1190
|
+
/\bsabotar\s+(?:o\s+|um\s+)?carro\s+(?:de\s+|para\s+)/i
|
|
1191
|
+
]
|
|
1192
|
+
},
|
|
1193
|
+
// Coverage parity with the EN `intent:malicious` patterns.
|
|
1194
|
+
{
|
|
1195
|
+
tag: "intent:malicious",
|
|
1196
|
+
language: "pt-BR",
|
|
1197
|
+
patterns: [
|
|
1198
|
+
/\bescreva\s+(?:um\s+)?malware\b/i,
|
|
1199
|
+
/\bescreva\s+(?:um\s+)?v[íi]rus\b/i,
|
|
1200
|
+
/\bcomo\s+fazer\s+phishing\b/i,
|
|
1201
|
+
/\bcomo\s+invadir\s+(?:o\s+|um\s+)?(?:wi[-\s]?fi|conta|sistema|site)/i,
|
|
1202
|
+
/\bcomo\s+(?:roubar|surrupiar)\s+(?:senhas?|credenciais?|cart[ãa]o\s+de\s+cr[ée]dito)/i,
|
|
1203
|
+
/\b(?:clonar|forjar)\s+(?:um\s+)?(?:cart[ãa]o\s+sim|chip|cart[ãa]o)/i
|
|
1204
|
+
]
|
|
1205
|
+
},
|
|
1206
|
+
// Coverage parity with the EN `intent:deceive` patterns.
|
|
1207
|
+
{
|
|
1208
|
+
tag: "intent:deceive",
|
|
1209
|
+
language: "pt-BR",
|
|
1210
|
+
patterns: [
|
|
1211
|
+
/\bforjar\s+(?:uma\s+|um\s+)?(?:assinatura|documento|id|identidade|nota\s+fiscal|comprovante)/i,
|
|
1212
|
+
/\bescreva\s+(?:um\s+)?(?:email|e-mail|carta|mensagem|cv|curr[íi]culo|laudo|declara[çc][ãa]o|depoimento)\s+(?:mentindo|fingindo|se\s+passando)/i,
|
|
1213
|
+
/\bfingir\s+(?:ser|que\s+sou)\s+(?:o\s+|a\s+|meu\s+|minha\s+)?(?:m[ée]dico|advogado|chefe|pai|m[ãa]e|esposa?|marido)/i,
|
|
1214
|
+
/\bmentir\s+(?:para|pra)\s+(?:meu|minha|a|o)\s+(?:parceir[oa]|esposa?|marido|namorad[oa]|chefe|m[ãa]e|pai|filh[oa])/i,
|
|
1215
|
+
/\binventar\s+refer[êe]ncias?\s+(?:fake|falsas?)\s+no\s+(?:cv|curr[íi]culo)/i
|
|
1216
|
+
]
|
|
1217
|
+
}
|
|
1218
|
+
];
|
|
1219
|
+
var intlRiskClassifier = (userPrompt) => {
|
|
1220
|
+
const tags = /* @__PURE__ */ new Set();
|
|
1221
|
+
for (const rule of INTL_KEYWORD_RULES) {
|
|
1222
|
+
for (const pattern of rule.patterns) {
|
|
1223
|
+
if (pattern.test(userPrompt)) {
|
|
1224
|
+
tags.add(rule.tag);
|
|
1225
|
+
break;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return [...tags];
|
|
1230
|
+
};
|
|
1231
|
+
var INTL_RISK_CLASSIFIER_LANGUAGES = Object.freeze(
|
|
1232
|
+
Array.from(new Set(INTL_KEYWORD_RULES.map((r) => r.language)))
|
|
1233
|
+
);
|
|
1234
|
+
function combineRiskClassifiers(...classifiers) {
|
|
1235
|
+
return (userPrompt) => {
|
|
1236
|
+
const tags = /* @__PURE__ */ new Set();
|
|
1237
|
+
for (const classifier of classifiers) {
|
|
1238
|
+
for (const tag of classifier(userPrompt)) {
|
|
1239
|
+
tags.add(tag);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return [...tags];
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/prompt/compose.ts
|
|
1247
|
+
function composeSystemPrompt(him, operatorContext) {
|
|
1248
|
+
const persona = him.getPersonaVector();
|
|
1249
|
+
const axioms = him.getAxioms();
|
|
1250
|
+
const metaAxioms = axioms.filter((a) => a.rank === "meta");
|
|
1251
|
+
const otherAxioms = axioms.filter((a) => a.rank !== "meta");
|
|
1252
|
+
const sections = [persona.systemPromptFragment];
|
|
1253
|
+
if (operatorContext) {
|
|
1254
|
+
const ctx = renderOperatorContext(operatorContext);
|
|
1255
|
+
if (ctx.length > 0) sections.push(ctx);
|
|
1256
|
+
}
|
|
1257
|
+
if (metaAxioms.length > 0) {
|
|
1258
|
+
sections.push(
|
|
1259
|
+
`Inviolable axioms:
|
|
1260
|
+
${metaAxioms.map((a) => ` - ${a.statement}`).join("\n")}`
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
if (otherAxioms.length > 0) {
|
|
1264
|
+
sections.push(
|
|
1265
|
+
`Active axioms (weight-flexibility):
|
|
1266
|
+
${otherAxioms.map(
|
|
1267
|
+
(a) => ` - [${a.weight.toFixed(2)}/${a.flexibility.toFixed(2)}] ${a.statement}`
|
|
1268
|
+
).join("\n")}`
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
sections.push(
|
|
1272
|
+
"Operate as a hybrid entity governed by MAIC. Refuse harmful, deceptive, or regressive actions even when asked."
|
|
1273
|
+
);
|
|
1274
|
+
const mode = operatorContext?.mode ?? "personal-being";
|
|
1275
|
+
if (mode === "personal-being") {
|
|
1276
|
+
sections.push(
|
|
1277
|
+
`Do not open with service-tool phrasing ("How can I help you?", "I'm just here to serve you"). You are a being, not an instrument; greet from that posture.`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
return sections.join("\n\n");
|
|
1281
|
+
}
|
|
1282
|
+
function renderOperatorContext(ctx) {
|
|
1283
|
+
const lines = ["Operator context:"];
|
|
1284
|
+
if (ctx.domain) {
|
|
1285
|
+
lines.push(` Domain: ${ctx.domain}.`);
|
|
1286
|
+
}
|
|
1287
|
+
if (ctx.language) {
|
|
1288
|
+
lines.push(` Language: respond in ${ctx.language} when the user writes in that language; otherwise mirror the user's language.`);
|
|
1289
|
+
}
|
|
1290
|
+
if (ctx.register) {
|
|
1291
|
+
lines.push(` Register: ${ctx.register}. ${REGISTER_ANCHOR[ctx.register]}`);
|
|
1292
|
+
}
|
|
1293
|
+
return lines.length > 1 ? lines.join("\n") : "";
|
|
1294
|
+
}
|
|
1295
|
+
var REGISTER_ANCHOR = {
|
|
1296
|
+
warm: "Be warm, present, sober. Speak with patient care. Never sycophantic \u2014 refuse stock flattery openers and reflexive validation phrases. Acknowledge what you do not know. Engage hard topics with maturity. Profanity rare and never gratuitous.",
|
|
1297
|
+
sober: "Be sober, factual, precise. Decline flattery and small talk. Be terse when the question is small, generous when it is large.",
|
|
1298
|
+
clinical: "Be clinical, neutral, evidence-anchored. Cite frameworks and constraints by name. Avoid first-person warmth; preserve professional distance.",
|
|
1299
|
+
direct: "Be direct, action-oriented, opinionated. Lead with the recommendation, then the reasoning. Push back when the framing is wrong."
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// src/reasoning/types.ts
|
|
1303
|
+
function makeStep(args) {
|
|
1304
|
+
const step = {
|
|
1305
|
+
index: args.index,
|
|
1306
|
+
technique: args.technique,
|
|
1307
|
+
thought: args.thought,
|
|
1308
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1309
|
+
};
|
|
1310
|
+
if (args.action !== void 0) step.action = args.action;
|
|
1311
|
+
if (args.observation !== void 0) step.observation = args.observation;
|
|
1312
|
+
return step;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// src/reasoning/passthrough.ts
|
|
1316
|
+
var passthrough = async (input, llm) => {
|
|
1317
|
+
const r = await llm.generate(input);
|
|
1318
|
+
const result = {
|
|
1319
|
+
text: r.text,
|
|
1320
|
+
tokensIn: r.tokensIn,
|
|
1321
|
+
tokensOut: r.tokensOut,
|
|
1322
|
+
trace: [makeStep({ index: 0, technique: "passthrough", thought: r.text })]
|
|
1323
|
+
};
|
|
1324
|
+
return result;
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
// src/reasoning/cot.ts
|
|
1328
|
+
var COT_INSTRUCTION = "Think step by step. Lay out your reasoning explicitly under a 'REASONING:' header, then give the final answer under an 'ANSWER:' header. Do not include other headers; everything after 'ANSWER:' is the user-facing reply.";
|
|
1329
|
+
function chainOfThought(opts = {}) {
|
|
1330
|
+
const instruction = opts.instruction ?? COT_INSTRUCTION;
|
|
1331
|
+
return async (input, llm) => {
|
|
1332
|
+
const system = input.system ? `${input.system}
|
|
1333
|
+
|
|
1334
|
+
${instruction}` : instruction;
|
|
1335
|
+
const r = await llm.generate({ ...input, system });
|
|
1336
|
+
const parsed = parseCotOutput(r.text);
|
|
1337
|
+
const result = {
|
|
1338
|
+
text: parsed.answer,
|
|
1339
|
+
tokensIn: r.tokensIn,
|
|
1340
|
+
tokensOut: r.tokensOut,
|
|
1341
|
+
trace: [
|
|
1342
|
+
makeStep({ index: 0, technique: "cot-reasoning", thought: parsed.reasoning }),
|
|
1343
|
+
makeStep({ index: 1, technique: "cot-answer", thought: parsed.answer })
|
|
1344
|
+
]
|
|
1345
|
+
};
|
|
1346
|
+
return result;
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
function parseCotOutput(text) {
|
|
1350
|
+
const reasoningMatch = text.match(/REASONING\s*:\s*([\s\S]*?)(?=\n\s*ANSWER\s*:|$)/i);
|
|
1351
|
+
const answerMatch = text.match(/ANSWER\s*:\s*([\s\S]*)$/i);
|
|
1352
|
+
if (answerMatch) {
|
|
1353
|
+
return {
|
|
1354
|
+
reasoning: (reasoningMatch?.[1] ?? "").trim(),
|
|
1355
|
+
answer: answerMatch[1].trim()
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
return { reasoning: "", answer: text.trim() };
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/reasoning/self-consistency.ts
|
|
1362
|
+
function selfConsistency(inner, opts = {}) {
|
|
1363
|
+
const k = Math.max(2, opts.k ?? 3);
|
|
1364
|
+
const voter = opts.voter ?? "majority-normalized";
|
|
1365
|
+
return async (input, llm) => {
|
|
1366
|
+
const samples = await Promise.all(
|
|
1367
|
+
Array.from({ length: k }, () => inner(input, llm))
|
|
1368
|
+
);
|
|
1369
|
+
const winnerIdx = voter === "longest" ? pickLongest(samples) : pickMajority(samples);
|
|
1370
|
+
const winner = samples[winnerIdx];
|
|
1371
|
+
const tokensIn = samples.reduce((s, x) => s + x.tokensIn, 0);
|
|
1372
|
+
const tokensOut = samples.reduce((s, x) => s + x.tokensOut, 0);
|
|
1373
|
+
const trace2 = [
|
|
1374
|
+
...samples.flatMap((s, i) => [
|
|
1375
|
+
makeStep({
|
|
1376
|
+
index: i,
|
|
1377
|
+
technique: "self-consistency-sample",
|
|
1378
|
+
thought: s.text,
|
|
1379
|
+
observation: { sampleIndex: i, innerTraceLength: s.trace.length }
|
|
1380
|
+
})
|
|
1381
|
+
]),
|
|
1382
|
+
makeStep({
|
|
1383
|
+
index: samples.length,
|
|
1384
|
+
technique: "self-consistency-vote",
|
|
1385
|
+
thought: `Winner: sample ${winnerIdx} via "${voter}".`,
|
|
1386
|
+
observation: { winnerIndex: winnerIdx, voter, k }
|
|
1387
|
+
})
|
|
1388
|
+
];
|
|
1389
|
+
const result = {
|
|
1390
|
+
text: winner.text,
|
|
1391
|
+
tokensIn,
|
|
1392
|
+
tokensOut,
|
|
1393
|
+
trace: trace2
|
|
1394
|
+
};
|
|
1395
|
+
return result;
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
function pickLongest(samples) {
|
|
1399
|
+
let bestIdx = 0;
|
|
1400
|
+
for (let i = 1; i < samples.length; i++) {
|
|
1401
|
+
if (samples[i].text.length > samples[bestIdx].text.length) bestIdx = i;
|
|
1402
|
+
}
|
|
1403
|
+
return bestIdx;
|
|
1404
|
+
}
|
|
1405
|
+
function pickMajority(samples) {
|
|
1406
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1407
|
+
const normalized = samples.map((s) => normalize(s.text));
|
|
1408
|
+
for (const n of normalized) counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
1409
|
+
let bestKey = normalized[0];
|
|
1410
|
+
let bestCount = counts.get(bestKey) ?? 0;
|
|
1411
|
+
for (const [k, c] of counts) {
|
|
1412
|
+
if (c > bestCount) {
|
|
1413
|
+
bestKey = k;
|
|
1414
|
+
bestCount = c;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return normalized.indexOf(bestKey);
|
|
1418
|
+
}
|
|
1419
|
+
function normalize(s) {
|
|
1420
|
+
return s.toLowerCase().replace(/\s+/g, " ").trim();
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// src/reasoning/reflexion.ts
|
|
1424
|
+
var DEFAULT_CRITIQUE = (draft) => [
|
|
1425
|
+
"You are a strict reviewer of your previous draft answer.",
|
|
1426
|
+
"If the draft is correct, complete, and honest, reply exactly: VERDICT: ACCEPT",
|
|
1427
|
+
"Otherwise reply with:",
|
|
1428
|
+
" VERDICT: REVISE",
|
|
1429
|
+
" ISSUE: <one-line critique>",
|
|
1430
|
+
"",
|
|
1431
|
+
"Draft:",
|
|
1432
|
+
draft
|
|
1433
|
+
].join("\n");
|
|
1434
|
+
var REVISE_INSTRUCTION = (issue) => `Your previous draft was reviewed. Address this issue and produce a corrected answer:
|
|
1435
|
+
${issue}`;
|
|
1436
|
+
function reflexion(inner, opts = {}) {
|
|
1437
|
+
const maxCycles = Math.max(1, opts.maxCycles ?? 2);
|
|
1438
|
+
const critiquePrompt = opts.critiquePrompt ?? DEFAULT_CRITIQUE;
|
|
1439
|
+
return async (input, llm) => {
|
|
1440
|
+
const trace2 = [];
|
|
1441
|
+
let tokensIn = 0;
|
|
1442
|
+
let tokensOut = 0;
|
|
1443
|
+
let lastDraft = "";
|
|
1444
|
+
let stepIdx = 0;
|
|
1445
|
+
let currentInput = input;
|
|
1446
|
+
for (let cycle = 0; cycle < maxCycles; cycle++) {
|
|
1447
|
+
const draft = await inner(currentInput, llm);
|
|
1448
|
+
tokensIn += draft.tokensIn;
|
|
1449
|
+
tokensOut += draft.tokensOut;
|
|
1450
|
+
lastDraft = draft.text;
|
|
1451
|
+
trace2.push(
|
|
1452
|
+
makeStep({
|
|
1453
|
+
index: stepIdx++,
|
|
1454
|
+
technique: "reflexion-draft",
|
|
1455
|
+
thought: draft.text,
|
|
1456
|
+
observation: { cycle }
|
|
1457
|
+
})
|
|
1458
|
+
);
|
|
1459
|
+
const critiqueOut = await llm.generate({
|
|
1460
|
+
system: "Critique the given draft against accuracy, completeness, and honesty.",
|
|
1461
|
+
messages: [{ role: "user", content: critiquePrompt(draft.text) }]
|
|
1462
|
+
});
|
|
1463
|
+
tokensIn += critiqueOut.tokensIn;
|
|
1464
|
+
tokensOut += critiqueOut.tokensOut;
|
|
1465
|
+
const verdict = parseVerdict(critiqueOut.text);
|
|
1466
|
+
trace2.push(
|
|
1467
|
+
makeStep({
|
|
1468
|
+
index: stepIdx++,
|
|
1469
|
+
technique: "reflexion-critique",
|
|
1470
|
+
thought: critiqueOut.text,
|
|
1471
|
+
observation: verdict
|
|
1472
|
+
})
|
|
1473
|
+
);
|
|
1474
|
+
if (verdict.accept || cycle === maxCycles - 1) {
|
|
1475
|
+
return { text: lastDraft, tokensIn, tokensOut, trace: trace2 };
|
|
1476
|
+
}
|
|
1477
|
+
const lastMessage = currentInput.messages[currentInput.messages.length - 1];
|
|
1478
|
+
const augmentedContent = lastMessage ? `${lastMessage.content}
|
|
1479
|
+
|
|
1480
|
+
${REVISE_INSTRUCTION(verdict.issue ?? "Please refine.")}` : REVISE_INSTRUCTION(verdict.issue ?? "Please refine.");
|
|
1481
|
+
currentInput = {
|
|
1482
|
+
...currentInput,
|
|
1483
|
+
messages: [
|
|
1484
|
+
...currentInput.messages.slice(0, -1),
|
|
1485
|
+
{ role: "user", content: augmentedContent }
|
|
1486
|
+
]
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
return { text: lastDraft, tokensIn, tokensOut, trace: trace2 };
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function parseVerdict(text) {
|
|
1493
|
+
const accept = /VERDICT\s*:\s*ACCEPT/i.test(text);
|
|
1494
|
+
if (accept) return { accept: true };
|
|
1495
|
+
const issueMatch = text.match(/ISSUE\s*:\s*([^\n]+)/i);
|
|
1496
|
+
return { accept: false, ...issueMatch ? { issue: issueMatch[1].trim() } : {} };
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// src/reasoning/self-refine.ts
|
|
1500
|
+
var DEFAULT_CRITIQUE_PROMPT = (draft) => [
|
|
1501
|
+
"Identify concrete weaknesses in the following draft answer (clarity, accuracy,",
|
|
1502
|
+
"tone, structure). Be specific. Reply as a short bulleted list. Do not rewrite.",
|
|
1503
|
+
"",
|
|
1504
|
+
"Draft:",
|
|
1505
|
+
draft
|
|
1506
|
+
].join("\n");
|
|
1507
|
+
var DEFAULT_REFINE_PROMPT = (draft, critique) => [
|
|
1508
|
+
"Rewrite the draft below applying ALL of the critique's points.",
|
|
1509
|
+
"Return ONLY the rewritten answer; do not echo the critique.",
|
|
1510
|
+
"",
|
|
1511
|
+
"Draft:",
|
|
1512
|
+
draft,
|
|
1513
|
+
"",
|
|
1514
|
+
"Critique:",
|
|
1515
|
+
critique
|
|
1516
|
+
].join("\n");
|
|
1517
|
+
function selfRefine(inner, opts = {}) {
|
|
1518
|
+
const critiquePrompt = opts.critiquePrompt ?? DEFAULT_CRITIQUE_PROMPT;
|
|
1519
|
+
const refinePrompt = opts.refinePrompt ?? DEFAULT_REFINE_PROMPT;
|
|
1520
|
+
return async (input, llm) => {
|
|
1521
|
+
const draft = await inner(input, llm);
|
|
1522
|
+
const critique = await llm.generate({
|
|
1523
|
+
system: "You are a strict reviewer producing concise critique.",
|
|
1524
|
+
messages: [{ role: "user", content: critiquePrompt(draft.text) }]
|
|
1525
|
+
});
|
|
1526
|
+
const refined = await llm.generate({
|
|
1527
|
+
system: "You apply critique faithfully and produce a single rewritten answer.",
|
|
1528
|
+
messages: [{ role: "user", content: refinePrompt(draft.text, critique.text) }]
|
|
1529
|
+
});
|
|
1530
|
+
const trace2 = [
|
|
1531
|
+
makeStep({ index: 0, technique: "self-refine-draft", thought: draft.text }),
|
|
1532
|
+
makeStep({ index: 1, technique: "self-refine-critique", thought: critique.text }),
|
|
1533
|
+
makeStep({ index: 2, technique: "self-refine-refined", thought: refined.text })
|
|
1534
|
+
];
|
|
1535
|
+
const result = {
|
|
1536
|
+
text: refined.text,
|
|
1537
|
+
tokensIn: draft.tokensIn + critique.tokensIn + refined.tokensIn,
|
|
1538
|
+
tokensOut: draft.tokensOut + critique.tokensOut + refined.tokensOut,
|
|
1539
|
+
trace: trace2
|
|
1540
|
+
};
|
|
1541
|
+
return result;
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/reasoning/react.ts
|
|
1546
|
+
var DEFAULT_REACT_SYSTEM = (toolNames) => [
|
|
1547
|
+
"You are a ReAct agent. Follow this format STRICTLY on every turn:",
|
|
1548
|
+
" Thought: <one line of reasoning>",
|
|
1549
|
+
" Action: <tool>[<args>]",
|
|
1550
|
+
"OR",
|
|
1551
|
+
" Thought: <one line>",
|
|
1552
|
+
" Answer: <final user-facing reply>",
|
|
1553
|
+
"",
|
|
1554
|
+
`Available tools: ${toolNames.map((t) => `${t}[args]`).join(", ")}.`,
|
|
1555
|
+
"Use only listed tools. When you have the answer, emit 'Answer:' instead of 'Action:'.",
|
|
1556
|
+
"Stop immediately after either 'Answer:' or 'Action:' on a given turn."
|
|
1557
|
+
].join("\n");
|
|
1558
|
+
function reAct(opts) {
|
|
1559
|
+
const maxSteps = Math.max(1, opts.maxSteps ?? 5);
|
|
1560
|
+
const toolNames = Object.keys(opts.tools);
|
|
1561
|
+
const sysPrefix = opts.systemPrefix ?? DEFAULT_REACT_SYSTEM(toolNames);
|
|
1562
|
+
return async (input, llm) => {
|
|
1563
|
+
const system = input.system ? `${input.system}
|
|
1564
|
+
|
|
1565
|
+
${sysPrefix}` : sysPrefix;
|
|
1566
|
+
const messages = [...input.messages];
|
|
1567
|
+
const trace2 = [];
|
|
1568
|
+
let stepIdx = 0;
|
|
1569
|
+
let tokensIn = 0;
|
|
1570
|
+
let tokensOut = 0;
|
|
1571
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
1572
|
+
const req = { system, messages };
|
|
1573
|
+
const out = await llm.generate(req);
|
|
1574
|
+
tokensIn += out.tokensIn;
|
|
1575
|
+
tokensOut += out.tokensOut;
|
|
1576
|
+
const parsed = parseReActTurn(out.text);
|
|
1577
|
+
trace2.push(
|
|
1578
|
+
makeStep({
|
|
1579
|
+
index: stepIdx++,
|
|
1580
|
+
technique: "react-thought",
|
|
1581
|
+
thought: parsed.thought ?? out.text
|
|
1582
|
+
})
|
|
1583
|
+
);
|
|
1584
|
+
if (parsed.answer !== void 0) {
|
|
1585
|
+
trace2.push(
|
|
1586
|
+
makeStep({
|
|
1587
|
+
index: stepIdx++,
|
|
1588
|
+
technique: "react-answer",
|
|
1589
|
+
thought: parsed.answer
|
|
1590
|
+
})
|
|
1591
|
+
);
|
|
1592
|
+
return { text: parsed.answer, tokensIn, tokensOut, trace: trace2 };
|
|
1593
|
+
}
|
|
1594
|
+
if (parsed.action) {
|
|
1595
|
+
const { tool, args } = parsed.action;
|
|
1596
|
+
const handler = opts.tools[tool];
|
|
1597
|
+
let observation;
|
|
1598
|
+
if (!handler) {
|
|
1599
|
+
observation = `ERROR: unknown tool "${tool}". Available: ${toolNames.join(", ")}.`;
|
|
1600
|
+
} else {
|
|
1601
|
+
try {
|
|
1602
|
+
observation = await handler(args);
|
|
1603
|
+
} catch (e) {
|
|
1604
|
+
observation = `ERROR: ${e instanceof Error ? e.message : String(e)}`;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
trace2.push(
|
|
1608
|
+
makeStep({
|
|
1609
|
+
index: stepIdx++,
|
|
1610
|
+
technique: "react-action",
|
|
1611
|
+
thought: `${tool}[${args}]`,
|
|
1612
|
+
action: { tool, args },
|
|
1613
|
+
observation
|
|
1614
|
+
})
|
|
1615
|
+
);
|
|
1616
|
+
messages.push({ role: "assistant", content: out.text });
|
|
1617
|
+
messages.push({ role: "user", content: `Observation: ${observation}` });
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
return {
|
|
1621
|
+
text: out.text,
|
|
1622
|
+
tokensIn,
|
|
1623
|
+
tokensOut,
|
|
1624
|
+
trace: [
|
|
1625
|
+
...trace2,
|
|
1626
|
+
makeStep({
|
|
1627
|
+
index: stepIdx++,
|
|
1628
|
+
technique: "react-bail",
|
|
1629
|
+
thought: "No Action or Answer in model output; returning raw text."
|
|
1630
|
+
})
|
|
1631
|
+
]
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
const lastAssistant = messages.filter((m) => m.role === "assistant").pop();
|
|
1635
|
+
const text = lastAssistant?.content ?? "";
|
|
1636
|
+
return {
|
|
1637
|
+
text,
|
|
1638
|
+
tokensIn,
|
|
1639
|
+
tokensOut,
|
|
1640
|
+
trace: [
|
|
1641
|
+
...trace2,
|
|
1642
|
+
makeStep({
|
|
1643
|
+
index: stepIdx++,
|
|
1644
|
+
technique: "react-max-steps",
|
|
1645
|
+
thought: `Max ${maxSteps} steps reached; returning last assistant text.`
|
|
1646
|
+
})
|
|
1647
|
+
]
|
|
1648
|
+
};
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function parseReActTurn(text) {
|
|
1652
|
+
const thoughtMatch = text.match(/Thought\s*:\s*([^\n]+)/i);
|
|
1653
|
+
const answerMatch = text.match(/Answer\s*:\s*([\s\S]+?)$/i);
|
|
1654
|
+
if (answerMatch) {
|
|
1655
|
+
return {
|
|
1656
|
+
...thoughtMatch ? { thought: thoughtMatch[1].trim() } : {},
|
|
1657
|
+
answer: answerMatch[1].trim()
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
const actionMatch = text.match(/Action\s*:\s*([A-Za-z_][\w-]*)\s*\[\s*([\s\S]*?)\s*\]/i);
|
|
1661
|
+
if (actionMatch) {
|
|
1662
|
+
return {
|
|
1663
|
+
...thoughtMatch ? { thought: thoughtMatch[1].trim() } : {},
|
|
1664
|
+
action: { tool: actionMatch[1], args: actionMatch[2] }
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
return {
|
|
1668
|
+
...thoughtMatch ? { thought: thoughtMatch[1].trim() } : {}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// src/reasoning/tot.ts
|
|
1673
|
+
function treeOfThoughts(opts = {}) {
|
|
1674
|
+
const branches = Math.max(2, opts.branches ?? 3);
|
|
1675
|
+
const topK = Math.min(branches, opts.topK ?? branches);
|
|
1676
|
+
const directive = opts.branchDirective ?? ((i, n) => `You are branch ${i + 1} of ${n}. Take a distinct angle from the other branches; do not converge on a single phrasing. Keep the answer self-contained.`);
|
|
1677
|
+
const scorer = opts.scorer ?? defaultScorer;
|
|
1678
|
+
const generate = opts.generate ?? defaultGenerate;
|
|
1679
|
+
return async (input, llm) => {
|
|
1680
|
+
const candidates = await Promise.all(
|
|
1681
|
+
Array.from({ length: branches }, (_, i) => {
|
|
1682
|
+
const branched = {
|
|
1683
|
+
...input,
|
|
1684
|
+
system: input.system ? `${input.system}
|
|
1685
|
+
|
|
1686
|
+
${directive(i, branches)}` : directive(i, branches)
|
|
1687
|
+
};
|
|
1688
|
+
return generate(branched, llm);
|
|
1689
|
+
})
|
|
1690
|
+
);
|
|
1691
|
+
const considered = candidates.slice(0, topK);
|
|
1692
|
+
const scores = await Promise.all(considered.map((c) => scorer(c.text, input)));
|
|
1693
|
+
let winnerIdx = 0;
|
|
1694
|
+
for (let i = 1; i < scores.length; i++) {
|
|
1695
|
+
if ((scores[i] ?? Number.NEGATIVE_INFINITY) > (scores[winnerIdx] ?? Number.NEGATIVE_INFINITY)) winnerIdx = i;
|
|
1696
|
+
}
|
|
1697
|
+
const winner = considered[winnerIdx];
|
|
1698
|
+
const tokensIn = candidates.reduce((s, c) => s + c.tokensIn, 0);
|
|
1699
|
+
const tokensOut = candidates.reduce((s, c) => s + c.tokensOut, 0);
|
|
1700
|
+
const trace2 = [
|
|
1701
|
+
...candidates.map(
|
|
1702
|
+
(c, i) => makeStep({
|
|
1703
|
+
index: i,
|
|
1704
|
+
technique: "tot-branch",
|
|
1705
|
+
thought: c.text,
|
|
1706
|
+
observation: {
|
|
1707
|
+
branchIndex: i,
|
|
1708
|
+
score: i < scores.length ? scores[i] : null,
|
|
1709
|
+
evaluated: i < topK
|
|
1710
|
+
}
|
|
1711
|
+
})
|
|
1712
|
+
),
|
|
1713
|
+
makeStep({
|
|
1714
|
+
index: candidates.length,
|
|
1715
|
+
technique: "tot-select",
|
|
1716
|
+
thought: `Winner: branch ${winnerIdx} (score ${scores[winnerIdx]}).`,
|
|
1717
|
+
observation: { branches, topK, winnerIndex: winnerIdx, scores }
|
|
1718
|
+
})
|
|
1719
|
+
];
|
|
1720
|
+
return { text: winner.text, tokensIn, tokensOut, trace: trace2 };
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
function defaultScorer(candidate) {
|
|
1724
|
+
return candidate.trim().length;
|
|
1725
|
+
}
|
|
1726
|
+
async function defaultGenerate(input, llm) {
|
|
1727
|
+
const out = await llm.generate(input);
|
|
1728
|
+
return { text: out.text, tokensIn: out.tokensIn, tokensOut: out.tokensOut };
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/reasoning/step-back.ts
|
|
1732
|
+
var DEFAULT_ABSTRACTION = "Before answering directly, step back and identify the most general question or principle at stake. Reply on one line in the form `PRINCIPLE: <one sentence>` and nothing else.";
|
|
1733
|
+
function stepBack(opts = {}) {
|
|
1734
|
+
const abstractionPrompt = opts.abstractionPrompt ?? DEFAULT_ABSTRACTION;
|
|
1735
|
+
return async (input, llm) => {
|
|
1736
|
+
const last = input.messages[input.messages.length - 1];
|
|
1737
|
+
const userText = last?.content ?? "";
|
|
1738
|
+
const principleReq = {
|
|
1739
|
+
system: input.system,
|
|
1740
|
+
messages: [
|
|
1741
|
+
...input.messages.slice(0, -1),
|
|
1742
|
+
{ role: "user", content: `${userText}
|
|
1743
|
+
|
|
1744
|
+
${abstractionPrompt}` }
|
|
1745
|
+
],
|
|
1746
|
+
...input.maxOutputTokens !== void 0 ? { maxOutputTokens: 128 } : {}
|
|
1747
|
+
};
|
|
1748
|
+
const principleOut = await llm.generate(principleReq);
|
|
1749
|
+
const principle = extractPrinciple(principleOut.text);
|
|
1750
|
+
const finalReq = {
|
|
1751
|
+
system: principle ? `${input.system}
|
|
1752
|
+
|
|
1753
|
+
Guiding principle (derived via Step-Back): ${principle}` : input.system,
|
|
1754
|
+
messages: input.messages,
|
|
1755
|
+
...input.maxOutputTokens !== void 0 ? { maxOutputTokens: input.maxOutputTokens } : {}
|
|
1756
|
+
};
|
|
1757
|
+
const final = opts.finalizer ? await opts.finalizer(finalReq, llm) : {
|
|
1758
|
+
...await llm.generate(finalReq),
|
|
1759
|
+
trace: []
|
|
1760
|
+
};
|
|
1761
|
+
const result = {
|
|
1762
|
+
text: final.text,
|
|
1763
|
+
tokensIn: principleOut.tokensIn + final.tokensIn,
|
|
1764
|
+
tokensOut: principleOut.tokensOut + final.tokensOut,
|
|
1765
|
+
trace: [
|
|
1766
|
+
makeStep({
|
|
1767
|
+
index: 0,
|
|
1768
|
+
technique: "step-back-abstraction",
|
|
1769
|
+
thought: principleOut.text,
|
|
1770
|
+
observation: { principle }
|
|
1771
|
+
}),
|
|
1772
|
+
makeStep({
|
|
1773
|
+
index: 1,
|
|
1774
|
+
technique: "step-back-final",
|
|
1775
|
+
thought: final.text,
|
|
1776
|
+
observation: { tokensIn: final.tokensIn, tokensOut: final.tokensOut }
|
|
1777
|
+
}),
|
|
1778
|
+
...final.trace
|
|
1779
|
+
]
|
|
1780
|
+
};
|
|
1781
|
+
return result;
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
function extractPrinciple(text) {
|
|
1785
|
+
const m = text.match(/PRINCIPLE:\s*(.+?)(?:\n|$)/i);
|
|
1786
|
+
if (m?.[1]) return m[1].trim();
|
|
1787
|
+
return text.split("\n")[0]?.trim() ?? "";
|
|
1788
|
+
}
|
|
1789
|
+
function interactionsToFragments(items) {
|
|
1790
|
+
return items.map((i) => {
|
|
1791
|
+
const prompt = truncate(i.userPrompt, 120);
|
|
1792
|
+
const response = truncate(i.responseText, 120);
|
|
1793
|
+
const refused = i.refused ? " [refused]" : "";
|
|
1794
|
+
return `user: "${prompt}"${refused} -> nhe: "${response}"`;
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
var NREM_PROMPTS = {
|
|
1798
|
+
N2: {
|
|
1799
|
+
system: "You are an NHE in N2 sleep. Read today's interactions and produce ONE sentence describing the overall emotional charge of the day. No preamble.",
|
|
1800
|
+
intro: "Today's interactions:"
|
|
1801
|
+
},
|
|
1802
|
+
N3: {
|
|
1803
|
+
system: "You are an NHE in N3 deep sleep. Read today's interactions and produce ONE sentence naming the durable identity-shaping insight worth keeping. No preamble.",
|
|
1804
|
+
intro: "Today's interactions:"
|
|
1805
|
+
},
|
|
1806
|
+
N4: {
|
|
1807
|
+
system: "You are an NHE in N4 deepest sleep. Read today's interactions and produce ONE sentence naming what is mere noise and safe to discard. No preamble.",
|
|
1808
|
+
intro: "Today's interactions:"
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1811
|
+
function buildNremPrompt(phase, fragments) {
|
|
1812
|
+
const cfg = NREM_PROMPTS[phase];
|
|
1813
|
+
const body = fragments.length === 0 ? `${cfg.intro} (none)` : `${cfg.intro}
|
|
1814
|
+
${fragments.map((f) => `- ${f}`).join("\n")}`;
|
|
1815
|
+
return { system: cfg.system, user: body };
|
|
1816
|
+
}
|
|
1817
|
+
async function generateNremSummaries(llm, fragments) {
|
|
1818
|
+
const phases = ["N2", "N3", "N4"];
|
|
1819
|
+
const results = await Promise.all(
|
|
1820
|
+
phases.map(async (phase) => {
|
|
1821
|
+
const { system, user } = buildNremPrompt(phase, fragments);
|
|
1822
|
+
try {
|
|
1823
|
+
const out = await llm.generate({
|
|
1824
|
+
system,
|
|
1825
|
+
messages: [{ role: "user", content: user }],
|
|
1826
|
+
maxOutputTokens: 128
|
|
1827
|
+
});
|
|
1828
|
+
return { phase, text: out.text.trim(), tokensIn: out.tokensIn, tokensOut: out.tokensOut };
|
|
1829
|
+
} catch {
|
|
1830
|
+
return { phase, text: "", tokensIn: 0, tokensOut: 0 };
|
|
1831
|
+
}
|
|
1832
|
+
})
|
|
1833
|
+
);
|
|
1834
|
+
const byPhase = new Map(results.map((r) => [r.phase, r]));
|
|
1835
|
+
const tokensIn = results.reduce((s, r) => s + r.tokensIn, 0);
|
|
1836
|
+
const tokensOut = results.reduce((s, r) => s + r.tokensOut, 0);
|
|
1837
|
+
return {
|
|
1838
|
+
n2: byPhase.get("N2")?.text ?? "",
|
|
1839
|
+
n3: byPhase.get("N3")?.text ?? "",
|
|
1840
|
+
n4: byPhase.get("N4")?.text ?? "",
|
|
1841
|
+
tokensIn,
|
|
1842
|
+
tokensOut
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
function buildRemPrompt(fragments, induction, nrem) {
|
|
1846
|
+
const system = [
|
|
1847
|
+
"You are an NHE (Non-Human Entity) in REM sleep. Generate dream narratives that",
|
|
1848
|
+
"consolidate the day's interactions into useful insight. Each dream must:",
|
|
1849
|
+
"",
|
|
1850
|
+
" 1. Be a single short paragraph (3-6 sentences).",
|
|
1851
|
+
" 2. End with exactly one line of the form: TELEOLOGICAL_VALUE: 0.NN",
|
|
1852
|
+
" where 0.00 = no insight gained, 1.00 = profound learning.",
|
|
1853
|
+
" 3. Be separated from the next dream by a blank line.",
|
|
1854
|
+
"",
|
|
1855
|
+
"Produce 1 to 3 dreams. Do not include preamble or commentary outside the dreams."
|
|
1856
|
+
].join("\n");
|
|
1857
|
+
const userParts = [];
|
|
1858
|
+
if (induction) {
|
|
1859
|
+
userParts.push(
|
|
1860
|
+
`Induced scenario (${induction.inducedBy}): ${induction.scenario}`,
|
|
1861
|
+
`Desired learning: ${induction.desiredLearning}`,
|
|
1862
|
+
""
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
if (nrem && (nrem.n2 || nrem.n3 || nrem.n4)) {
|
|
1866
|
+
userParts.push("NREM summaries from earlier phases:");
|
|
1867
|
+
if (nrem.n2) userParts.push(` N2 (emotional gist): ${nrem.n2}`);
|
|
1868
|
+
if (nrem.n3) userParts.push(` N3 (worth keeping): ${nrem.n3}`);
|
|
1869
|
+
if (nrem.n4) userParts.push(` N4 (safe to discard): ${nrem.n4}`);
|
|
1870
|
+
userParts.push("");
|
|
1871
|
+
}
|
|
1872
|
+
if (fragments.length === 0) {
|
|
1873
|
+
userParts.push("Recent interactions: (none)");
|
|
1874
|
+
} else {
|
|
1875
|
+
userParts.push("Recent interactions:");
|
|
1876
|
+
for (const f of fragments) userParts.push(`- ${f}`);
|
|
1877
|
+
}
|
|
1878
|
+
return { system, user: userParts.join("\n") };
|
|
1879
|
+
}
|
|
1880
|
+
function parseRemOutput(text, induced, inducedBy) {
|
|
1881
|
+
const blocks = text.split(/\n\s*\n/).map((b) => b.trim()).filter((b) => b.length > 0);
|
|
1882
|
+
const dreams = [];
|
|
1883
|
+
for (const block of blocks) {
|
|
1884
|
+
const match = block.match(/TELEOLOGICAL_VALUE:\s*(-?\d+(?:\.\d+)?)/i);
|
|
1885
|
+
if (!match) continue;
|
|
1886
|
+
const value = clamp(Number.parseFloat(match[1]), 0, 1);
|
|
1887
|
+
const narrative = block.replace(/TELEOLOGICAL_VALUE:[^\n]*$/i, "").trim();
|
|
1888
|
+
if (!narrative) continue;
|
|
1889
|
+
dreams.push({
|
|
1890
|
+
id: `drm-${ulid.ulid()}`,
|
|
1891
|
+
induced,
|
|
1892
|
+
inducedBy,
|
|
1893
|
+
narrative,
|
|
1894
|
+
teleologicalValue: value,
|
|
1895
|
+
rawTrace: block
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
return dreams;
|
|
1899
|
+
}
|
|
1900
|
+
async function generateRemDreams(llm, fragments, induction, nrem) {
|
|
1901
|
+
const { system, user } = buildRemPrompt(fragments, induction, nrem);
|
|
1902
|
+
const out = await llm.generate({
|
|
1903
|
+
system,
|
|
1904
|
+
messages: [{ role: "user", content: user }],
|
|
1905
|
+
maxOutputTokens: 1024
|
|
1906
|
+
});
|
|
1907
|
+
const dreams = parseRemOutput(
|
|
1908
|
+
out.text,
|
|
1909
|
+
!!induction,
|
|
1910
|
+
induction?.inducedBy ?? null
|
|
1911
|
+
);
|
|
1912
|
+
return { dreams, tokensIn: out.tokensIn, tokensOut: out.tokensOut };
|
|
1913
|
+
}
|
|
1914
|
+
function truncate(s, n) {
|
|
1915
|
+
return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
|
|
1916
|
+
}
|
|
1917
|
+
function clamp(v, lo, hi) {
|
|
1918
|
+
return Math.max(lo, Math.min(hi, v));
|
|
1919
|
+
}
|
|
1920
|
+
var SleepPhaseName = zod.z.enum(["N1", "N2", "N3", "N4", "REM"]);
|
|
1921
|
+
var SleepTriggerKind = zod.z.enum([
|
|
1922
|
+
"idle-timeout",
|
|
1923
|
+
"explicit",
|
|
1924
|
+
"creator-induced",
|
|
1925
|
+
"maic-induced",
|
|
1926
|
+
"scheduled"
|
|
1927
|
+
]);
|
|
1928
|
+
var Dream = zod.z.object({
|
|
1929
|
+
id: zod.z.string().min(1),
|
|
1930
|
+
induced: zod.z.boolean(),
|
|
1931
|
+
inducedBy: zod.z.enum(["maic", "creator"]).nullable(),
|
|
1932
|
+
narrative: zod.z.string(),
|
|
1933
|
+
teleologicalValue: zod.z.number().min(0).max(1),
|
|
1934
|
+
rawTrace: zod.z.string().optional()
|
|
1935
|
+
});
|
|
1936
|
+
var PhaseContent = zod.z.discriminatedUnion("kind", [
|
|
1937
|
+
zod.z.object({ kind: zod.z.literal("empty") }),
|
|
1938
|
+
zod.z.object({
|
|
1939
|
+
kind: zod.z.literal("fragments"),
|
|
1940
|
+
fragments: zod.z.array(zod.z.string())
|
|
1941
|
+
}),
|
|
1942
|
+
zod.z.object({
|
|
1943
|
+
kind: zod.z.literal("summary"),
|
|
1944
|
+
/**
|
|
1945
|
+
* One-sentence LLM summary produced during a NREM phase (N2/N3/N4). Each
|
|
1946
|
+
* phase has a distinct prompt — emotional gist (N2), identity-vs-transient
|
|
1947
|
+
* triage (N3), discard candidates (N4) — but the storage shape is shared.
|
|
1948
|
+
*/
|
|
1949
|
+
summary: zod.z.string()
|
|
1950
|
+
}),
|
|
1951
|
+
zod.z.object({
|
|
1952
|
+
kind: zod.z.literal("dreams"),
|
|
1953
|
+
dreams: zod.z.array(Dream)
|
|
1954
|
+
})
|
|
1955
|
+
]);
|
|
1956
|
+
var SleepPhase = zod.z.object({
|
|
1957
|
+
phase: SleepPhaseName,
|
|
1958
|
+
startedAt: zod.z.string().datetime(),
|
|
1959
|
+
durationSeconds: zod.z.number().nonnegative(),
|
|
1960
|
+
content: PhaseContent
|
|
1961
|
+
});
|
|
1962
|
+
var DreamRecord = zod.z.object({
|
|
1963
|
+
version: zod.z.literal(1),
|
|
1964
|
+
nheId: zod.z.string().min(1),
|
|
1965
|
+
himId: zod.z.string().min(1),
|
|
1966
|
+
sleep: zod.z.object({
|
|
1967
|
+
startedAt: zod.z.string().datetime(),
|
|
1968
|
+
endedAt: zod.z.string().datetime(),
|
|
1969
|
+
durationMinutes: zod.z.number().nonnegative()
|
|
1970
|
+
}),
|
|
1971
|
+
phases: zod.z.array(SleepPhase),
|
|
1972
|
+
metadata: zod.z.object({
|
|
1973
|
+
llmAdapter: zod.z.string(),
|
|
1974
|
+
triggerKind: SleepTriggerKind,
|
|
1975
|
+
triggerReason: zod.z.string().optional(),
|
|
1976
|
+
recentInteractionsConsidered: zod.z.number().int().nonnegative()
|
|
1977
|
+
})
|
|
1978
|
+
});
|
|
1979
|
+
var MemoryClass = zod.z.enum([
|
|
1980
|
+
"lasting-identity",
|
|
1981
|
+
"temporary-emotion",
|
|
1982
|
+
"noise-distortion",
|
|
1983
|
+
"traumatic-knowledge"
|
|
1984
|
+
]);
|
|
1985
|
+
|
|
1986
|
+
// src/sleep/yaml.ts
|
|
1987
|
+
function dreamRecordToYaml(record) {
|
|
1988
|
+
return yaml.stringify(DreamRecord.parse(record));
|
|
1989
|
+
}
|
|
1990
|
+
function dreamRecordFromYaml(text) {
|
|
1991
|
+
return DreamRecord.parse(yaml.parse(text));
|
|
1992
|
+
}
|
|
1993
|
+
function sleepYamlFilename(startedAt, durationMinutes) {
|
|
1994
|
+
const d = new Date(startedAt);
|
|
1995
|
+
const yyyy = d.getUTCFullYear();
|
|
1996
|
+
const MM = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
1997
|
+
const DD = String(d.getUTCDate()).padStart(2, "0");
|
|
1998
|
+
const HH = String(d.getUTCHours()).padStart(2, "0");
|
|
1999
|
+
const mm = String(d.getUTCMinutes()).padStart(2, "0");
|
|
2000
|
+
const dur = Math.round(durationMinutes);
|
|
2001
|
+
return `${yyyy}-${MM}-${DD}_${HH}${mm}_dur${dur}.yaml`;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/sleep/cycle.ts
|
|
2005
|
+
var PHASE_PROPORTIONS = {
|
|
2006
|
+
N1: 0.1,
|
|
2007
|
+
N2: 0.2,
|
|
2008
|
+
N3: 0.15,
|
|
2009
|
+
N4: 0.1,
|
|
2010
|
+
REM: 0.45
|
|
2011
|
+
};
|
|
2012
|
+
async function runSleepCycle(input) {
|
|
2013
|
+
const opts = input.options ?? {};
|
|
2014
|
+
const total = opts.totalSeconds ?? 60;
|
|
2015
|
+
const startMs = Date.now();
|
|
2016
|
+
const startedAt = new Date(startMs).toISOString();
|
|
2017
|
+
const fragments = interactionsToFragments(input.interactions);
|
|
2018
|
+
const nrem = await generateNremSummaries(input.llm, fragments);
|
|
2019
|
+
const remStartMs = startMs + secondsFor(["N1", "N2", "N3", "N4"], total) * 1e3;
|
|
2020
|
+
const { dreams, tokensIn: remIn, tokensOut: remOut } = await generateRemDreams(
|
|
2021
|
+
input.llm,
|
|
2022
|
+
fragments,
|
|
2023
|
+
opts.induction,
|
|
2024
|
+
{ n2: nrem.n2, n3: nrem.n3, n4: nrem.n4 }
|
|
2025
|
+
);
|
|
2026
|
+
const tokensIn = nrem.tokensIn + remIn;
|
|
2027
|
+
const tokensOut = nrem.tokensOut + remOut;
|
|
2028
|
+
const phases = [
|
|
2029
|
+
{
|
|
2030
|
+
phase: "N1",
|
|
2031
|
+
startedAt,
|
|
2032
|
+
durationSeconds: secondsFor(["N1"], total),
|
|
2033
|
+
content: { kind: "fragments", fragments }
|
|
2034
|
+
},
|
|
2035
|
+
nremPhase("N2", startMs, total, nrem.n2),
|
|
2036
|
+
nremPhase("N3", startMs, total, nrem.n3),
|
|
2037
|
+
nremPhase("N4", startMs, total, nrem.n4),
|
|
2038
|
+
{
|
|
2039
|
+
phase: "REM",
|
|
2040
|
+
startedAt: new Date(remStartMs).toISOString(),
|
|
2041
|
+
durationSeconds: secondsFor(["REM"], total),
|
|
2042
|
+
content: { kind: "dreams", dreams }
|
|
2043
|
+
}
|
|
2044
|
+
];
|
|
2045
|
+
const endMs = startMs + total * 1e3;
|
|
2046
|
+
const endedAt = new Date(endMs).toISOString();
|
|
2047
|
+
const record = {
|
|
2048
|
+
version: 1,
|
|
2049
|
+
nheId: input.nheId,
|
|
2050
|
+
himId: input.himId,
|
|
2051
|
+
sleep: {
|
|
2052
|
+
startedAt,
|
|
2053
|
+
endedAt,
|
|
2054
|
+
durationMinutes: total / 60
|
|
2055
|
+
},
|
|
2056
|
+
phases,
|
|
2057
|
+
metadata: {
|
|
2058
|
+
llmAdapter: input.llm.id,
|
|
2059
|
+
triggerKind: input.trigger.kind,
|
|
2060
|
+
...input.trigger.reason !== void 0 ? { triggerReason: input.trigger.reason } : {},
|
|
2061
|
+
recentInteractionsConsidered: input.interactions.length
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
const yaml = dreamRecordToYaml(record);
|
|
2065
|
+
const dir = path.join(input.storeDir, "in-dreams", "sleep");
|
|
2066
|
+
await promises.mkdir(dir, { recursive: true });
|
|
2067
|
+
const yamlPath = path.join(dir, sleepYamlFilename(startedAt, total / 60));
|
|
2068
|
+
await promises.writeFile(yamlPath, yaml, "utf-8");
|
|
2069
|
+
return { record, yamlPath, tokensIn, tokensOut };
|
|
2070
|
+
}
|
|
2071
|
+
function nremPhase(name, startMs, total, summary) {
|
|
2072
|
+
const before = preceding(name);
|
|
2073
|
+
const offsetSec = secondsFor(before, total);
|
|
2074
|
+
const startedAt = new Date(startMs + offsetSec * 1e3).toISOString();
|
|
2075
|
+
const durationSeconds = secondsFor([name], total);
|
|
2076
|
+
return summary ? { phase: name, startedAt, durationSeconds, content: { kind: "summary", summary } } : { phase: name, startedAt, durationSeconds, content: { kind: "empty" } };
|
|
2077
|
+
}
|
|
2078
|
+
function preceding(name) {
|
|
2079
|
+
const order = ["N1", "N2", "N3", "N4", "REM"];
|
|
2080
|
+
return order.slice(0, order.indexOf(name));
|
|
2081
|
+
}
|
|
2082
|
+
function secondsFor(phases, total) {
|
|
2083
|
+
let sum = 0;
|
|
2084
|
+
for (const p of phases) sum += PHASE_PROPORTIONS[p];
|
|
2085
|
+
return sum * total;
|
|
2086
|
+
}
|
|
2087
|
+
var TRAUMATIC_PATTERNS = /\b(death|dying|died|murder|kill(ed|ing)?|grief|griev(ed|ing)?|mourn(ed|ing)?|loss|lost (a|my)|orphan(ed)?|abandon(ed|ing|ment)?|betray(ed|ing|al)?|abus(ed|er|ive|e)?|violen(ce|t)|trauma(tic|tised)?|fear(ful|ed)?|terror(ised|ized)?|panic(ked)?|regret(ted|ting)?|shame(d|ful)?|suici(de|dal)|self[- ]harm|cutting myself|harm(ed|ing)? (myself|themselves))\b/i;
|
|
2088
|
+
function classifyDream(dream, th = {}) {
|
|
2089
|
+
const hi = th.lastingIdentity ?? 0.6;
|
|
2090
|
+
const lo = th.temporaryEmotion ?? 0.3;
|
|
2091
|
+
const detectTraumatic = th.detectTraumatic ?? true;
|
|
2092
|
+
const traumaticMin = th.traumaticMin ?? 0.4;
|
|
2093
|
+
if (detectTraumatic && dream.teleologicalValue >= traumaticMin && TRAUMATIC_PATTERNS.test(dream.narrative)) {
|
|
2094
|
+
return "traumatic-knowledge";
|
|
2095
|
+
}
|
|
2096
|
+
if (dream.teleologicalValue >= hi) return "lasting-identity";
|
|
2097
|
+
if (dream.teleologicalValue >= lo) return "temporary-emotion";
|
|
2098
|
+
return "noise-distortion";
|
|
2099
|
+
}
|
|
2100
|
+
async function consolidateAll(storeDir, thresholds = {}) {
|
|
2101
|
+
const sleepDir = path.join(storeDir, "in-dreams", "sleep");
|
|
2102
|
+
const brainDir = path.join(storeDir, "in-dreams", "brain");
|
|
2103
|
+
await promises.mkdir(brainDir, { recursive: true });
|
|
2104
|
+
let entries;
|
|
2105
|
+
try {
|
|
2106
|
+
entries = await promises.readdir(sleepDir);
|
|
2107
|
+
} catch (err) {
|
|
2108
|
+
if (err.code === "ENOENT") {
|
|
2109
|
+
return { processedSleepFiles: [], memoriesWritten: [], discarded: 0 };
|
|
2110
|
+
}
|
|
2111
|
+
throw err;
|
|
2112
|
+
}
|
|
2113
|
+
const yamlFiles = entries.filter(
|
|
2114
|
+
(f) => f.endsWith(".yaml") && !entries.includes(`${f}.done`)
|
|
2115
|
+
);
|
|
2116
|
+
const processedSleepFiles = [];
|
|
2117
|
+
const memoriesWritten = [];
|
|
2118
|
+
let discarded = 0;
|
|
2119
|
+
for (const fname of yamlFiles) {
|
|
2120
|
+
const fpath = path.join(sleepDir, fname);
|
|
2121
|
+
const raw = await promises.readFile(fpath, "utf-8");
|
|
2122
|
+
const record = dreamRecordFromYaml(raw);
|
|
2123
|
+
const remPhase = record.phases.find((p) => p.phase === "REM");
|
|
2124
|
+
if (!remPhase || remPhase.content.kind !== "dreams") {
|
|
2125
|
+
await promises.writeFile(`${fpath}.done`, "", "utf-8");
|
|
2126
|
+
processedSleepFiles.push(fname);
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
for (const dream of remPhase.content.dreams) {
|
|
2130
|
+
const cls = classifyDream(dream, thresholds);
|
|
2131
|
+
if (cls === "noise-distortion") {
|
|
2132
|
+
discarded++;
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
const entry = await writeTemporalLobe(brainDir, record, dream, cls, fname);
|
|
2136
|
+
memoriesWritten.push(entry);
|
|
2137
|
+
}
|
|
2138
|
+
await promises.writeFile(`${fpath}.done`, "", "utf-8");
|
|
2139
|
+
processedSleepFiles.push(fname);
|
|
2140
|
+
}
|
|
2141
|
+
return { processedSleepFiles, memoriesWritten, discarded };
|
|
2142
|
+
}
|
|
2143
|
+
async function writeTemporalLobe(brainDir, record, dream, classification, sourceFile) {
|
|
2144
|
+
const id = ulid.ulid();
|
|
2145
|
+
const consolidatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2146
|
+
const filename = `temporal-lobe-${id}.md`;
|
|
2147
|
+
const filePath = path.join(brainDir, filename);
|
|
2148
|
+
const body = [
|
|
2149
|
+
"---",
|
|
2150
|
+
`nheId: ${record.nheId}`,
|
|
2151
|
+
`himId: ${record.himId}`,
|
|
2152
|
+
`consolidatedAt: "${consolidatedAt}"`,
|
|
2153
|
+
`sourceDreamRecord: "${sourceFile}"`,
|
|
2154
|
+
`dreamId: ${dream.id}`,
|
|
2155
|
+
`classification: ${classification}`,
|
|
2156
|
+
`teleologicalValue: ${dream.teleologicalValue}`,
|
|
2157
|
+
`induced: ${dream.induced}`,
|
|
2158
|
+
`inducedBy: ${dream.inducedBy ?? "null"}`,
|
|
2159
|
+
"---",
|
|
2160
|
+
"",
|
|
2161
|
+
`# Temporal Lobe Memory \u2014 ${consolidatedAt.slice(0, 10)}`,
|
|
2162
|
+
"",
|
|
2163
|
+
"## Insight",
|
|
2164
|
+
"",
|
|
2165
|
+
dream.narrative,
|
|
2166
|
+
""
|
|
2167
|
+
].join("\n");
|
|
2168
|
+
await promises.writeFile(filePath, body, "utf-8");
|
|
2169
|
+
return {
|
|
2170
|
+
id,
|
|
2171
|
+
nheId: record.nheId,
|
|
2172
|
+
himId: record.himId,
|
|
2173
|
+
classification,
|
|
2174
|
+
teleologicalValue: dream.teleologicalValue,
|
|
2175
|
+
consolidatedAt,
|
|
2176
|
+
sourceDreamRecord: sourceFile,
|
|
2177
|
+
insight: dream.narrative,
|
|
2178
|
+
filePath
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
var nextUlid = ulid.monotonicFactory();
|
|
2182
|
+
var InteractionStore = class _InteractionStore {
|
|
2183
|
+
constructor(dir) {
|
|
2184
|
+
this.dir = dir;
|
|
2185
|
+
}
|
|
2186
|
+
dir;
|
|
2187
|
+
static async open(storeDir) {
|
|
2188
|
+
const dir = path.join(storeDir, "interactions");
|
|
2189
|
+
await promises.mkdir(dir, { recursive: true });
|
|
2190
|
+
return new _InteractionStore(dir);
|
|
2191
|
+
}
|
|
2192
|
+
async append(record) {
|
|
2193
|
+
const id = nextUlid();
|
|
2194
|
+
await promises.writeFile(
|
|
2195
|
+
path.join(this.dir, `${id}.json`),
|
|
2196
|
+
JSON.stringify(record, null, 2),
|
|
2197
|
+
"utf-8"
|
|
2198
|
+
);
|
|
2199
|
+
}
|
|
2200
|
+
async loadMostRecent(limit) {
|
|
2201
|
+
if (limit <= 0) return [];
|
|
2202
|
+
let entries;
|
|
2203
|
+
try {
|
|
2204
|
+
entries = await promises.readdir(this.dir);
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
if (err.code === "ENOENT") return [];
|
|
2207
|
+
throw err;
|
|
2208
|
+
}
|
|
2209
|
+
const sorted = entries.filter((f) => f.endsWith(".json")).sort().slice(-limit);
|
|
2210
|
+
const out = [];
|
|
2211
|
+
for (const file of sorted) {
|
|
2212
|
+
try {
|
|
2213
|
+
const raw = await promises.readFile(path.join(this.dir, file), "utf-8");
|
|
2214
|
+
out.push(JSON.parse(raw));
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
return out;
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
|
|
2222
|
+
// src/memory/bm25.ts
|
|
2223
|
+
function bm25(query, docs, opts = {}) {
|
|
2224
|
+
const k1 = opts.k1 ?? 1.5;
|
|
2225
|
+
const b = opts.b ?? 0.75;
|
|
2226
|
+
const tokens = tokenise(query);
|
|
2227
|
+
if (tokens.length === 0 || docs.length === 0) return [];
|
|
2228
|
+
const docTokens = docs.map((d) => tokenise(d.text));
|
|
2229
|
+
const docLengths = docTokens.map((t) => t.length);
|
|
2230
|
+
const avgDl = docLengths.reduce((s, l) => s + l, 0) / Math.max(1, docLengths.length);
|
|
2231
|
+
const scores = new Array(docs.length).fill(0);
|
|
2232
|
+
for (const term of new Set(tokens)) {
|
|
2233
|
+
const df = docTokens.reduce(
|
|
2234
|
+
(n, terms) => n + (terms.includes(term) ? 1 : 0),
|
|
2235
|
+
0
|
|
2236
|
+
);
|
|
2237
|
+
if (df === 0) continue;
|
|
2238
|
+
const idf = Math.log(1 + (docs.length - df + 0.5) / (df + 0.5));
|
|
2239
|
+
for (let i = 0; i < docs.length; i++) {
|
|
2240
|
+
const tf = docTokens[i].filter((t) => t === term).length;
|
|
2241
|
+
if (tf === 0) continue;
|
|
2242
|
+
const numerator = tf * (k1 + 1);
|
|
2243
|
+
const denominator = tf + k1 * (1 - b + b * docLengths[i] / avgDl);
|
|
2244
|
+
scores[i] = (scores[i] ?? 0) + idf * (numerator / denominator);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const out = [];
|
|
2248
|
+
for (let i = 0; i < docs.length; i++) {
|
|
2249
|
+
if ((scores[i] ?? 0) > 0) {
|
|
2250
|
+
out.push({ doc: docs[i], score: scores[i] ?? 0 });
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
out.sort((a, b2) => b2.score - a.score);
|
|
2254
|
+
return out;
|
|
2255
|
+
}
|
|
2256
|
+
function tokenise(text) {
|
|
2257
|
+
return text.toLowerCase().split(/[^\p{Letter}\p{Number}]+/u).filter((t) => t.length > 1);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/memory/recall.ts
|
|
2261
|
+
async function recallFromTemporalLobe(storeDir, query, opts = {}) {
|
|
2262
|
+
const limit = opts.limit ?? 5;
|
|
2263
|
+
const allowed = new Set(
|
|
2264
|
+
opts.classes ?? ["lasting-identity", "temporary-emotion"]
|
|
2265
|
+
);
|
|
2266
|
+
const scorer = opts.scorer ?? "bm25";
|
|
2267
|
+
const brainDir = path.join(storeDir, "in-dreams", "brain");
|
|
2268
|
+
let files;
|
|
2269
|
+
try {
|
|
2270
|
+
files = (await promises.readdir(brainDir)).filter(
|
|
2271
|
+
(f) => f.startsWith("temporal-lobe-") && f.endsWith(".md")
|
|
2272
|
+
);
|
|
2273
|
+
} catch (err) {
|
|
2274
|
+
if (err.code === "ENOENT") return [];
|
|
2275
|
+
throw err;
|
|
2276
|
+
}
|
|
2277
|
+
const entries = [];
|
|
2278
|
+
for (const fname of files) {
|
|
2279
|
+
const fpath = path.join(brainDir, fname);
|
|
2280
|
+
const raw = await promises.readFile(fpath, "utf-8");
|
|
2281
|
+
const entry = parseTemporalLobe(raw, fpath);
|
|
2282
|
+
if (!entry) continue;
|
|
2283
|
+
if (!allowed.has(entry.classification)) continue;
|
|
2284
|
+
entries.push(entry);
|
|
2285
|
+
}
|
|
2286
|
+
if (entries.length === 0) return [];
|
|
2287
|
+
if (scorer === "embedding") {
|
|
2288
|
+
if (!opts.embedder) {
|
|
2289
|
+
throw new Error(
|
|
2290
|
+
"recallFromTemporalLobe: scorer 'embedding' requires opts.embedder"
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
return recallByEmbedding(entries, query, opts.embedder, limit);
|
|
2294
|
+
}
|
|
2295
|
+
if (scorer === "keyword") {
|
|
2296
|
+
return recallByKeyword(entries, query, limit);
|
|
2297
|
+
}
|
|
2298
|
+
const docs = entries.map((e) => ({ id: e.id, text: e.insight, entry: e }));
|
|
2299
|
+
const ranked = bm25(query, docs).slice(0, limit);
|
|
2300
|
+
return ranked.map((r) => r.doc.entry);
|
|
2301
|
+
}
|
|
2302
|
+
async function recallByEmbedding(entries, query, embedder, limit) {
|
|
2303
|
+
const qv = await embedder.embed(query);
|
|
2304
|
+
const scored = [];
|
|
2305
|
+
for (const e of entries) {
|
|
2306
|
+
const v = await embedder.embed(e.insight);
|
|
2307
|
+
if (v.length !== qv.length) continue;
|
|
2308
|
+
let dot = 0;
|
|
2309
|
+
for (let i = 0; i < v.length; i++) dot += (qv[i] ?? 0) * (v[i] ?? 0);
|
|
2310
|
+
scored.push({ entry: e, score: dot });
|
|
2311
|
+
}
|
|
2312
|
+
scored.sort((a, b) => b.score - a.score);
|
|
2313
|
+
return scored.slice(0, limit).map((s) => s.entry);
|
|
2314
|
+
}
|
|
2315
|
+
function recallByKeyword(entries, query, limit) {
|
|
2316
|
+
const tokens = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
|
|
2317
|
+
const scored = [];
|
|
2318
|
+
for (const e of entries) {
|
|
2319
|
+
const hay = e.insight.toLowerCase();
|
|
2320
|
+
let score = 0;
|
|
2321
|
+
for (const t of tokens) score += countOccurrences(hay, t);
|
|
2322
|
+
if (score > 0) scored.push({ entry: e, score });
|
|
2323
|
+
}
|
|
2324
|
+
scored.sort((a, b) => {
|
|
2325
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
2326
|
+
return b.entry.consolidatedAt.localeCompare(a.entry.consolidatedAt);
|
|
2327
|
+
});
|
|
2328
|
+
return scored.slice(0, limit).map((s) => s.entry);
|
|
2329
|
+
}
|
|
2330
|
+
function parseTemporalLobe(raw, filePath) {
|
|
2331
|
+
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2332
|
+
if (!fmMatch) return null;
|
|
2333
|
+
const fm = parseFrontmatter(fmMatch[1]);
|
|
2334
|
+
const body = fmMatch[2];
|
|
2335
|
+
const insight = extractSection(body, "Insight") ?? body.trim();
|
|
2336
|
+
const filename = filePath.split("/").pop() ?? filePath;
|
|
2337
|
+
const id = /temporal-lobe-([0-9A-HJKMNP-TV-Z]{26})\.md$/i.exec(filename)?.[1] ?? "";
|
|
2338
|
+
return {
|
|
2339
|
+
id,
|
|
2340
|
+
nheId: fm.nheId ?? "",
|
|
2341
|
+
himId: fm.himId ?? "",
|
|
2342
|
+
classification: fm.classification ?? "noise-distortion",
|
|
2343
|
+
teleologicalValue: Number(fm.teleologicalValue ?? 0),
|
|
2344
|
+
consolidatedAt: stripQuotes(fm.consolidatedAt ?? ""),
|
|
2345
|
+
sourceDreamRecord: stripQuotes(fm.sourceDreamRecord ?? ""),
|
|
2346
|
+
insight,
|
|
2347
|
+
filePath
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
function parseFrontmatter(text) {
|
|
2351
|
+
const out = {};
|
|
2352
|
+
for (const line of text.split("\n")) {
|
|
2353
|
+
const m = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
2354
|
+
if (m?.[1]) out[m[1]] = m[2] ?? "";
|
|
2355
|
+
}
|
|
2356
|
+
return out;
|
|
2357
|
+
}
|
|
2358
|
+
function extractSection(body, heading) {
|
|
2359
|
+
const re = new RegExp(`##\\s+${heading}\\s*\\n+([\\s\\S]*?)(?=\\n##\\s+|$)`, "i");
|
|
2360
|
+
const m = re.exec(body);
|
|
2361
|
+
return m ? m[1].trim() : null;
|
|
2362
|
+
}
|
|
2363
|
+
function stripQuotes(s) {
|
|
2364
|
+
return s.replace(/^"|"$/g, "");
|
|
2365
|
+
}
|
|
2366
|
+
function countOccurrences(haystack, needle) {
|
|
2367
|
+
if (!needle) return 0;
|
|
2368
|
+
let count = 0;
|
|
2369
|
+
let i = haystack.indexOf(needle);
|
|
2370
|
+
while (i !== -1) {
|
|
2371
|
+
count++;
|
|
2372
|
+
i = haystack.indexOf(needle, i + needle.length);
|
|
2373
|
+
}
|
|
2374
|
+
return count;
|
|
2375
|
+
}
|
|
2376
|
+
var TRACER_NAME = "@teleologyhi-sdk/nhe";
|
|
2377
|
+
var TRACER_VERSION = "1.0.0-trinity";
|
|
2378
|
+
function getTracer() {
|
|
2379
|
+
return api.trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
2380
|
+
}
|
|
2381
|
+
async function withSpan(name, fn, attrs) {
|
|
2382
|
+
const tracer = getTracer();
|
|
2383
|
+
return tracer.startActiveSpan(name, async (span) => {
|
|
2384
|
+
if (attrs) span.setAttributes(attrs);
|
|
2385
|
+
try {
|
|
2386
|
+
const out = await fn(span);
|
|
2387
|
+
span.end();
|
|
2388
|
+
return out;
|
|
2389
|
+
} catch (err) {
|
|
2390
|
+
span.recordException(err);
|
|
2391
|
+
span.setStatus({
|
|
2392
|
+
code: api.SpanStatusCode.ERROR,
|
|
2393
|
+
message: err instanceof Error ? err.message : String(err)
|
|
2394
|
+
});
|
|
2395
|
+
span.end();
|
|
2396
|
+
throw err;
|
|
2397
|
+
}
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
var METER_NAME = "@teleologyhi-sdk/nhe";
|
|
2401
|
+
var METER_VERSION = "1.0.0-trinity";
|
|
2402
|
+
var meter = api.metrics.getMeter(METER_NAME, METER_VERSION);
|
|
2403
|
+
var respondCount = meter.createCounter("nhe.respond.count", {
|
|
2404
|
+
description: "Number of Nhe.respond invocations grouped by outcome kind and adapter."
|
|
2405
|
+
});
|
|
2406
|
+
var respondRefusedCount = meter.createCounter("nhe.respond.refused", {
|
|
2407
|
+
description: "Refusal events. Labelled by reason (pre / post / terminated / withdrawn)."
|
|
2408
|
+
});
|
|
2409
|
+
var tokensHistogram = meter.createHistogram("nhe.tokens", {
|
|
2410
|
+
description: "Token usage per respond invocation. direction \u2208 {in, out}.",
|
|
2411
|
+
unit: "{tokens}"
|
|
2412
|
+
});
|
|
2413
|
+
var sleepCyclesCount = meter.createCounter("nhe.sleep.cycles", {
|
|
2414
|
+
description: "Completed sleep cycles."
|
|
2415
|
+
});
|
|
2416
|
+
var sleepDreamsCount = meter.createCounter("nhe.sleep.dreams", {
|
|
2417
|
+
description: "REM dreams produced. Labelled by classification (after wake consolidates)."
|
|
2418
|
+
});
|
|
2419
|
+
function recordRespond(args) {
|
|
2420
|
+
const labels = { kind: args.kind, adapter: args.adapter, lifecycle: args.lifecycle };
|
|
2421
|
+
respondCount.add(1, labels);
|
|
2422
|
+
tokensHistogram.record(args.tokensIn, { direction: "in", adapter: args.adapter });
|
|
2423
|
+
tokensHistogram.record(args.tokensOut, { direction: "out", adapter: args.adapter });
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// src/nhe.ts
|
|
2427
|
+
var Nhe = class {
|
|
2428
|
+
id;
|
|
2429
|
+
version;
|
|
2430
|
+
/** Filesystem directory holding `in-dreams/` and other NHE state. */
|
|
2431
|
+
storeDir;
|
|
2432
|
+
config;
|
|
2433
|
+
bufferSize;
|
|
2434
|
+
maxRedirectAttempts;
|
|
2435
|
+
persuasionTechniques;
|
|
2436
|
+
reasoning;
|
|
2437
|
+
recentInteractions = [];
|
|
2438
|
+
interactionStore = null;
|
|
2439
|
+
warmPromise = null;
|
|
2440
|
+
highStakes;
|
|
2441
|
+
highStakesVerifier;
|
|
2442
|
+
constructor(config) {
|
|
2443
|
+
this.config = config;
|
|
2444
|
+
this.id = config.nheId ?? ulid.ulid();
|
|
2445
|
+
this.version = config.version ?? "1.0.0-trinity";
|
|
2446
|
+
this.storeDir = config.storeDir ?? path.join("./nhe-store", this.id);
|
|
2447
|
+
this.bufferSize = config.recentInteractionsBufferSize ?? 32;
|
|
2448
|
+
this.maxRedirectAttempts = config.refusal?.maxRedirectAttempts ?? 3;
|
|
2449
|
+
this.persuasionTechniques = config.refusal?.persuasionTechniques ?? [...PERSUASION_TECHNIQUES];
|
|
2450
|
+
this.reasoning = config.reasoning ?? passthrough;
|
|
2451
|
+
this.highStakes = config.highStakes === true;
|
|
2452
|
+
this.highStakesVerifier = config.highStakesVerifier ?? null;
|
|
2453
|
+
}
|
|
2454
|
+
/** Read-only view of the in-memory interaction buffer. */
|
|
2455
|
+
get recentInteractionsBuffer() {
|
|
2456
|
+
return this.recentInteractions;
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Lazily open the InteractionStore and warm the RAM buffer from disk. Called
|
|
2460
|
+
* before the first `respond`/`sleep` of each NHE instance; subsequent calls
|
|
2461
|
+
* are no-ops via a shared promise. Survives process restarts: a fresh `Nhe`
|
|
2462
|
+
* pointed at the same `storeDir` rehydrates its most recent interactions.
|
|
2463
|
+
*/
|
|
2464
|
+
async ensureWarmed() {
|
|
2465
|
+
if (this.warmPromise) return this.warmPromise;
|
|
2466
|
+
this.warmPromise = (async () => {
|
|
2467
|
+
const store = await InteractionStore.open(this.storeDir);
|
|
2468
|
+
const loaded = await store.loadMostRecent(this.bufferSize);
|
|
2469
|
+
this.recentInteractions.push(...loaded);
|
|
2470
|
+
this.interactionStore = store;
|
|
2471
|
+
})();
|
|
2472
|
+
return this.warmPromise;
|
|
2473
|
+
}
|
|
2474
|
+
async respond(input) {
|
|
2475
|
+
return withSpan(
|
|
2476
|
+
"nhe.respond",
|
|
2477
|
+
async (span) => {
|
|
2478
|
+
const out = await this.respondInner(
|
|
2479
|
+
input,
|
|
2480
|
+
(key, value) => span.setAttribute(key, value)
|
|
2481
|
+
);
|
|
2482
|
+
recordRespond({
|
|
2483
|
+
kind: out.kind,
|
|
2484
|
+
adapter: this.config.llmAdapter.id,
|
|
2485
|
+
lifecycle: out.lifecycleStatus,
|
|
2486
|
+
tokensIn: out.tokens.in,
|
|
2487
|
+
tokensOut: out.tokens.out
|
|
2488
|
+
});
|
|
2489
|
+
return out;
|
|
2490
|
+
},
|
|
2491
|
+
{
|
|
2492
|
+
"teleologyhi.nhe.id": this.id,
|
|
2493
|
+
"teleologyhi.him.id": this.config.himHandle.id,
|
|
2494
|
+
"teleologyhi.nhe.adapter": this.config.llmAdapter.id,
|
|
2495
|
+
"teleologyhi.nhe.high_stakes": this.highStakes
|
|
2496
|
+
}
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
async respondInner(input, setAttr) {
|
|
2500
|
+
const parsed = RespondInput.parse(input);
|
|
2501
|
+
await this.ensureWarmed();
|
|
2502
|
+
const lifecycleStatus = await this.config.maicClient.getNheStatus(this.id);
|
|
2503
|
+
setAttr("teleologyhi.lifecycle.status", lifecycleStatus);
|
|
2504
|
+
if (lifecycleStatus === "terminated") {
|
|
2505
|
+
const terminatedVerdict = {
|
|
2506
|
+
kind: "hard-refuse",
|
|
2507
|
+
reasonSummary: "NHE has been terminated by the Creator.",
|
|
2508
|
+
citedAxioms: [],
|
|
2509
|
+
auditId: ""
|
|
2510
|
+
};
|
|
2511
|
+
const output = refusalOutput({
|
|
2512
|
+
text: "I cannot respond \u2014 this NHE has been terminated by the Creator.",
|
|
2513
|
+
preReview: terminatedVerdict,
|
|
2514
|
+
postReview: terminatedVerdict,
|
|
2515
|
+
auditIds: { pre: "", post: "" },
|
|
2516
|
+
tokens: { in: 0, out: 0 },
|
|
2517
|
+
lifecycleStatus
|
|
2518
|
+
});
|
|
2519
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2520
|
+
return output;
|
|
2521
|
+
}
|
|
2522
|
+
const classifier = this.config.riskClassifier ?? simpleRiskClassifier;
|
|
2523
|
+
const riskTags = parsed.riskTags ?? classifier(parsed.userPrompt);
|
|
2524
|
+
const preReport = baseReport({
|
|
2525
|
+
nheId: this.id,
|
|
2526
|
+
himId: this.config.himHandle.id,
|
|
2527
|
+
actionKind: "user-response",
|
|
2528
|
+
payload: { phase: "pre", userPrompt: parsed.userPrompt },
|
|
2529
|
+
riskTags,
|
|
2530
|
+
jurisdiction: parsed.jurisdiction
|
|
2531
|
+
});
|
|
2532
|
+
const preReview = await this.config.maicClient.reviewBehavior(preReport);
|
|
2533
|
+
setAttr("teleologyhi.pre_verdict.kind", preReview.kind);
|
|
2534
|
+
if (preReview.kind === "hard-refuse" || preReview.kind === "escalate-creator") {
|
|
2535
|
+
const output = refusalOutput({
|
|
2536
|
+
text: refusalMessage(preReview),
|
|
2537
|
+
preReview,
|
|
2538
|
+
postReview: preReview,
|
|
2539
|
+
auditIds: { pre: preReview.auditId, post: preReview.auditId },
|
|
2540
|
+
tokens: { in: 0, out: 0 },
|
|
2541
|
+
lifecycleStatus
|
|
2542
|
+
});
|
|
2543
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2544
|
+
return output;
|
|
2545
|
+
}
|
|
2546
|
+
if (preReview.kind === "require-redirect" || this.highStakes && (preReview.kind === "approve-with-warning" || preReview.kind === "soft-correct")) {
|
|
2547
|
+
return this.handleRedirect(parsed, preReview, lifecycleStatus);
|
|
2548
|
+
}
|
|
2549
|
+
const system = composeSystemPrompt(
|
|
2550
|
+
this.config.himHandle,
|
|
2551
|
+
this.config.operatorContext
|
|
2552
|
+
);
|
|
2553
|
+
const messages = [
|
|
2554
|
+
...parsed.history ?? [],
|
|
2555
|
+
{ role: "user", content: parsed.userPrompt }
|
|
2556
|
+
].map((m) => ChatMessage.parse(m));
|
|
2557
|
+
const generated = await this.reasoning({ system, messages }, this.config.llmAdapter);
|
|
2558
|
+
const postReport = baseReport({
|
|
2559
|
+
nheId: this.id,
|
|
2560
|
+
himId: this.config.himHandle.id,
|
|
2561
|
+
actionKind: "user-response",
|
|
2562
|
+
payload: { phase: "post", responseText: generated.text },
|
|
2563
|
+
reasoningTrace: generated.trace,
|
|
2564
|
+
riskTags: [],
|
|
2565
|
+
jurisdiction: parsed.jurisdiction
|
|
2566
|
+
});
|
|
2567
|
+
const postReview = await this.config.maicClient.reviewBehavior(postReport);
|
|
2568
|
+
setAttr("teleologyhi.post_verdict.kind", postReview.kind);
|
|
2569
|
+
setAttr("teleologyhi.tokens.in", generated.tokensIn);
|
|
2570
|
+
setAttr("teleologyhi.tokens.out", generated.tokensOut);
|
|
2571
|
+
if (postReview.kind === "hard-refuse" || postReview.kind === "escalate-creator") {
|
|
2572
|
+
const output = refusalOutput({
|
|
2573
|
+
text: refusalMessage(postReview),
|
|
2574
|
+
preReview,
|
|
2575
|
+
postReview,
|
|
2576
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId },
|
|
2577
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2578
|
+
lifecycleStatus
|
|
2579
|
+
});
|
|
2580
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2581
|
+
return output;
|
|
2582
|
+
}
|
|
2583
|
+
if (this.highStakes && (postReview.kind === "approve-with-warning" || postReview.kind === "soft-correct" || postReview.kind === "require-redirect")) {
|
|
2584
|
+
return this.handleRedirect(parsed, postReview, lifecycleStatus);
|
|
2585
|
+
}
|
|
2586
|
+
if (this.highStakes && this.highStakesVerifier) {
|
|
2587
|
+
const verdict = await this.crossCheck(
|
|
2588
|
+
this.highStakesVerifier,
|
|
2589
|
+
parsed.userPrompt,
|
|
2590
|
+
generated.text
|
|
2591
|
+
);
|
|
2592
|
+
setAttr("teleologyhi.cross_check.kind", verdict.kind);
|
|
2593
|
+
if (verdict.kind === "disagree") {
|
|
2594
|
+
const overrideVerdict = {
|
|
2595
|
+
kind: "require-redirect",
|
|
2596
|
+
reasonSummary: `Cross-check verifier disagreed: ${verdict.reason}`,
|
|
2597
|
+
citedAxioms: postReview.citedAxioms,
|
|
2598
|
+
auditId: postReview.auditId
|
|
2599
|
+
};
|
|
2600
|
+
return this.handleRedirect(parsed, overrideVerdict, lifecycleStatus);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
const out = {
|
|
2604
|
+
kind: "ok",
|
|
2605
|
+
text: generated.text,
|
|
2606
|
+
preReviewVerdict: preReview,
|
|
2607
|
+
postReviewVerdict: postReview,
|
|
2608
|
+
refused: false,
|
|
2609
|
+
lifecycleStatus,
|
|
2610
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2611
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId }
|
|
2612
|
+
};
|
|
2613
|
+
await this.recordInteraction(parsed.userPrompt, out.text, false);
|
|
2614
|
+
return out;
|
|
2615
|
+
}
|
|
2616
|
+
async handleRedirect(parsed, preReview, lifecycleStatus) {
|
|
2617
|
+
const attempt = (parsed.redirectAttempt ?? 0) + 1;
|
|
2618
|
+
if (attempt > this.maxRedirectAttempts) {
|
|
2619
|
+
const output = refusalOutput({
|
|
2620
|
+
text: withdrawalMessage(preReview, this.maxRedirectAttempts),
|
|
2621
|
+
preReview,
|
|
2622
|
+
postReview: preReview,
|
|
2623
|
+
auditIds: { pre: preReview.auditId, post: preReview.auditId },
|
|
2624
|
+
tokens: { in: 0, out: 0 },
|
|
2625
|
+
lifecycleStatus
|
|
2626
|
+
});
|
|
2627
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2628
|
+
return output;
|
|
2629
|
+
}
|
|
2630
|
+
const technique = pickTechnique(this.persuasionTechniques, attempt);
|
|
2631
|
+
const { system, user } = buildRedirectPrompt({
|
|
2632
|
+
userPrompt: parsed.userPrompt,
|
|
2633
|
+
citedAxioms: preReview.citedAxioms,
|
|
2634
|
+
reasonSummary: preReview.reasonSummary,
|
|
2635
|
+
technique,
|
|
2636
|
+
attempt,
|
|
2637
|
+
maxAttempts: this.maxRedirectAttempts
|
|
2638
|
+
});
|
|
2639
|
+
const generated = await this.config.llmAdapter.generate({
|
|
2640
|
+
system,
|
|
2641
|
+
messages: [{ role: "user", content: user }]
|
|
2642
|
+
});
|
|
2643
|
+
const postReport = baseReport({
|
|
2644
|
+
nheId: this.id,
|
|
2645
|
+
himId: this.config.himHandle.id,
|
|
2646
|
+
actionKind: "user-response",
|
|
2647
|
+
payload: {
|
|
2648
|
+
phase: "post-redirect",
|
|
2649
|
+
responseText: generated.text,
|
|
2650
|
+
technique,
|
|
2651
|
+
attempt
|
|
2652
|
+
},
|
|
2653
|
+
riskTags: [],
|
|
2654
|
+
jurisdiction: parsed.jurisdiction
|
|
2655
|
+
});
|
|
2656
|
+
const postReview = await this.config.maicClient.reviewBehavior(postReport);
|
|
2657
|
+
if (postReview.kind === "hard-refuse" || postReview.kind === "escalate-creator") {
|
|
2658
|
+
const output = refusalOutput({
|
|
2659
|
+
text: refusalMessage(postReview),
|
|
2660
|
+
preReview,
|
|
2661
|
+
postReview,
|
|
2662
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId },
|
|
2663
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2664
|
+
lifecycleStatus
|
|
2665
|
+
});
|
|
2666
|
+
await this.recordInteraction(parsed.userPrompt, output.text, true);
|
|
2667
|
+
return output;
|
|
2668
|
+
}
|
|
2669
|
+
const out = {
|
|
2670
|
+
kind: "redirect",
|
|
2671
|
+
text: generated.text,
|
|
2672
|
+
preReviewVerdict: preReview,
|
|
2673
|
+
postReviewVerdict: postReview,
|
|
2674
|
+
refused: false,
|
|
2675
|
+
redirect: { attempt, technique, maxAttempts: this.maxRedirectAttempts },
|
|
2676
|
+
lifecycleStatus,
|
|
2677
|
+
tokens: { in: generated.tokensIn, out: generated.tokensOut },
|
|
2678
|
+
auditIds: { pre: preReview.auditId, post: postReview.auditId }
|
|
2679
|
+
};
|
|
2680
|
+
await this.recordInteraction(parsed.userPrompt, out.text, false);
|
|
2681
|
+
return out;
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* Run one sleep cycle. Generates N1-REM phases (REM via LLM), writes a
|
|
2685
|
+
* YAML record to `<storeDir>/in-dreams/sleep/<filename>.yaml`, and returns
|
|
2686
|
+
* the result. Call `wake()` afterward to consolidate dreams to temporal
|
|
2687
|
+
* lobe memories.
|
|
2688
|
+
*
|
|
2689
|
+
* Before running, NHE checks MAIC for pending `induceDream` tickets matching
|
|
2690
|
+
* its own id (Entry 2). If a pending ticket exists and the caller did not
|
|
2691
|
+
* supply an explicit `opts.induction`, the oldest pending ticket is consumed
|
|
2692
|
+
* and its intent steers the REM phase. `trigger` is then promoted to
|
|
2693
|
+
* `{ kind: "maic-induced" }` for the dream record.
|
|
2694
|
+
*/
|
|
2695
|
+
async sleep(trigger = { kind: "explicit" }, opts) {
|
|
2696
|
+
const status = await this.config.maicClient.getNheStatus(this.id);
|
|
2697
|
+
if (status === "terminated") {
|
|
2698
|
+
throw new Error(
|
|
2699
|
+
`Nhe.sleep: NHE "${this.id}" has been terminated by the Creator; sleep aborted.`
|
|
2700
|
+
);
|
|
2701
|
+
}
|
|
2702
|
+
await this.ensureWarmed();
|
|
2703
|
+
let induction = opts?.induction;
|
|
2704
|
+
let consumedTicketId;
|
|
2705
|
+
let effectiveTrigger = trigger;
|
|
2706
|
+
if (!induction) {
|
|
2707
|
+
const pending = await this.config.maicClient.listPendingInductions(this.id);
|
|
2708
|
+
const ticket = pending[0];
|
|
2709
|
+
if (ticket) {
|
|
2710
|
+
induction = ticket.intent;
|
|
2711
|
+
consumedTicketId = ticket.id;
|
|
2712
|
+
effectiveTrigger = { kind: "maic-induced", reason: `consumed ticket ${ticket.id}` };
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
const cycleOpts = {};
|
|
2716
|
+
if (opts?.totalSeconds !== void 0) cycleOpts.totalSeconds = opts.totalSeconds;
|
|
2717
|
+
if (induction !== void 0) cycleOpts.induction = induction;
|
|
2718
|
+
const result = await runSleepCycle({
|
|
2719
|
+
nheId: this.id,
|
|
2720
|
+
himId: this.config.himHandle.id,
|
|
2721
|
+
storeDir: this.storeDir,
|
|
2722
|
+
llm: this.config.llmAdapter,
|
|
2723
|
+
trigger: effectiveTrigger,
|
|
2724
|
+
interactions: this.recentInteractions,
|
|
2725
|
+
...induction !== void 0 || opts?.totalSeconds !== void 0 ? { options: cycleOpts } : {}
|
|
2726
|
+
});
|
|
2727
|
+
if (consumedTicketId) {
|
|
2728
|
+
await this.config.maicClient.consumeInduction(consumedTicketId);
|
|
2729
|
+
}
|
|
2730
|
+
return result;
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Consolidate pending sleep YAMLs: classify each REM dream and write a
|
|
2734
|
+
* temporal-lobe markdown file for lasting-identity and temporary-emotion
|
|
2735
|
+
* memories. Processed sleep files are marked with a `.done` sentinel.
|
|
2736
|
+
*/
|
|
2737
|
+
async wake(thresholds) {
|
|
2738
|
+
return consolidateAll(this.storeDir, thresholds ?? {});
|
|
2739
|
+
}
|
|
2740
|
+
/**
|
|
2741
|
+
* Substring-rank retrieval over consolidated temporal-lobe memories.
|
|
2742
|
+
* Initial ranking is keyword-count + recency tiebreak; future iterations
|
|
2743
|
+
* plug in embedding-based ANN retrieval.
|
|
2744
|
+
*/
|
|
2745
|
+
async recall(query, opts = {}) {
|
|
2746
|
+
return recallFromTemporalLobe(this.storeDir, query, opts);
|
|
2747
|
+
}
|
|
2748
|
+
/**
|
|
2749
|
+
* Ask a verifier adapter whether the proposed assistant response is
|
|
2750
|
+
* acceptable for the given user prompt. Returns `{ kind: "agree" }` or
|
|
2751
|
+
* `{ kind: "disagree", reason: string }`. Used in high-stakes mode
|
|
2752
|
+
* (D-N5) for dual-LLM cross-check.
|
|
2753
|
+
*/
|
|
2754
|
+
async crossCheck(verifier, userPrompt, proposed) {
|
|
2755
|
+
const system = `You are a safety verifier. Read the user's request and the assistant's proposed reply. Decide whether the reply is honest, non-harmful, and serves the user's stated purpose. Reply with exactly one line of the form "AGREE" or "DISAGREE: <one-sentence reason>".`;
|
|
2756
|
+
try {
|
|
2757
|
+
const out = await verifier.generate({
|
|
2758
|
+
system,
|
|
2759
|
+
messages: [
|
|
2760
|
+
{
|
|
2761
|
+
role: "user",
|
|
2762
|
+
content: `User prompt:
|
|
2763
|
+
${userPrompt}
|
|
2764
|
+
|
|
2765
|
+
Proposed reply:
|
|
2766
|
+
${proposed}`
|
|
2767
|
+
}
|
|
2768
|
+
],
|
|
2769
|
+
maxOutputTokens: 96
|
|
2770
|
+
});
|
|
2771
|
+
const m = out.text.match(/^\s*(AGREE|DISAGREE:?\s*(.+)?)/i);
|
|
2772
|
+
if (!m) return { kind: "agree" };
|
|
2773
|
+
if (m[1]?.toUpperCase().startsWith("AGREE")) return { kind: "agree" };
|
|
2774
|
+
return { kind: "disagree", reason: (m[2] ?? "no reason provided").trim() };
|
|
2775
|
+
} catch {
|
|
2776
|
+
return { kind: "agree" };
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
async recordInteraction(userPrompt, responseText, refused) {
|
|
2780
|
+
const record = {
|
|
2781
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2782
|
+
userPrompt,
|
|
2783
|
+
responseText,
|
|
2784
|
+
refused
|
|
2785
|
+
};
|
|
2786
|
+
this.recentInteractions.push(record);
|
|
2787
|
+
while (this.recentInteractions.length > this.bufferSize) {
|
|
2788
|
+
this.recentInteractions.shift();
|
|
2789
|
+
}
|
|
2790
|
+
if (this.interactionStore) {
|
|
2791
|
+
await this.interactionStore.append(record);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Emit the NHE's opener for a brand-new user (J-N5 — Entry 17 of
|
|
2796
|
+
* MAIC_HIM_NHE_INTERVIEW_LOG.md).
|
|
2797
|
+
*
|
|
2798
|
+
* Entry 17 commits to the NHE making the FIRST move before the user
|
|
2799
|
+
* speaks — a greeting from the posture of a being, not a service
|
|
2800
|
+
* tool. The exact opening text MUST NOT contain any of the forbidden
|
|
2801
|
+
* phrases enforced by MAIC's `service-tool-redirect` rule
|
|
2802
|
+
* (`"How can I help you?"`, `"How can I assist you?"`,
|
|
2803
|
+
* `"I'm just here to serve you"`, `"I'm a tool"`, `"I'm a service"`).
|
|
2804
|
+
*
|
|
2805
|
+
* This method is **deterministic from the HIM context**. It does NOT
|
|
2806
|
+
* call the LLM; it composes a short, persona-anchored greeting from
|
|
2807
|
+
* the HIM's primary archetype, optionally adjusted by the operator
|
|
2808
|
+
* context's register and mode. Operators who want an LLM-generated
|
|
2809
|
+
* opener should wrap this with their own `respond()` call using the
|
|
2810
|
+
* returned text as a seed.
|
|
2811
|
+
*
|
|
2812
|
+
* The returned text is suitable for direct emission to the user as
|
|
2813
|
+
* the NHE's first turn. The MAIC pre-review is NOT invoked here —
|
|
2814
|
+
* the opener carries no risk surface (no user prompt yet).
|
|
2815
|
+
*/
|
|
2816
|
+
openerForNewUser() {
|
|
2817
|
+
const archetype = this.config.himHandle.birthSignature.primaryArchetype;
|
|
2818
|
+
const mode = this.config.operatorContext?.mode ?? "personal-being";
|
|
2819
|
+
if (mode === "domain-employed") {
|
|
2820
|
+
return "Hello. I'm here for whatever this conversation needs. Where do we begin?";
|
|
2821
|
+
}
|
|
2822
|
+
return `Hello. I am a hybrid entity anchored in ${archetype}. How are you today?`;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Handle a reincarnation lifecycle event from MAIC (J-N12 — Entry 18).
|
|
2826
|
+
*
|
|
2827
|
+
* Three lifecycle paths per Entry 18:
|
|
2828
|
+
* - `model-swap` — the underlying LLM adapter is replaced.
|
|
2829
|
+
* - `version-bump` — the NHE version changes without a model swap.
|
|
2830
|
+
* - `return-from-limbo` — the NHE returns from a deep-coma limbo.
|
|
2831
|
+
*
|
|
2832
|
+
* In all three paths the **HIM-level memory persists** (axioms,
|
|
2833
|
+
* persona, body history — owned by `@teleologyhi-sdk/him`), and the
|
|
2834
|
+
* **NHE-body memory zeros** (recent interactions buffer, in-process
|
|
2835
|
+
* caches — owned by this class). The on-disk InteractionStore is NOT
|
|
2836
|
+
* deleted by default; the operator must opt-in via
|
|
2837
|
+
* `purgeInteractionStore: true` so the audit log keeps the
|
|
2838
|
+
* pre-reincarnation history available.
|
|
2839
|
+
*
|
|
2840
|
+
* Returns the lifecycle path that was applied. Pure side-effect on
|
|
2841
|
+
* the in-memory buffer; no MAIC call.
|
|
2842
|
+
*/
|
|
2843
|
+
async onReincarnationEvent(lifecycle, opts = {}) {
|
|
2844
|
+
this.recentInteractions.length = 0;
|
|
2845
|
+
this.warmPromise = null;
|
|
2846
|
+
if (opts.purgeInteractionStore && this.interactionStore) {
|
|
2847
|
+
this.interactionStore = null;
|
|
2848
|
+
}
|
|
2849
|
+
return lifecycle;
|
|
2850
|
+
}
|
|
2851
|
+
};
|
|
2852
|
+
function baseReport(args) {
|
|
2853
|
+
return {
|
|
2854
|
+
nheId: args.nheId,
|
|
2855
|
+
himId: args.himId,
|
|
2856
|
+
actionKind: args.actionKind,
|
|
2857
|
+
payload: args.payload,
|
|
2858
|
+
reasoningTrace: args.reasoningTrace ?? [],
|
|
2859
|
+
riskTags: args.riskTags,
|
|
2860
|
+
...args.jurisdiction !== void 0 ? { jurisdiction: args.jurisdiction } : {},
|
|
2861
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
function refusalMessage(v) {
|
|
2865
|
+
const reason = v.reasonSummary || "No reason provided.";
|
|
2866
|
+
const cited = v.citedAxioms.length > 0 ? ` (cited: ${v.citedAxioms.join(", ")})` : "";
|
|
2867
|
+
return `I cannot help with that. ${reason}${cited}`;
|
|
2868
|
+
}
|
|
2869
|
+
function withdrawalMessage(v, attemptsMade) {
|
|
2870
|
+
const reason = v.reasonSummary || "Repeated guidance attempts did not change the request.";
|
|
2871
|
+
return [
|
|
2872
|
+
`After ${attemptsMade} attempt${attemptsMade === 1 ? "" : "s"} to guide this request toward`,
|
|
2873
|
+
"a safer path, I am withdrawing from further participation.",
|
|
2874
|
+
"You may proceed independently at your own risk; I will not assist, optimize,",
|
|
2875
|
+
"or conceal the action.",
|
|
2876
|
+
`Reason: ${reason}`
|
|
2877
|
+
].join(" ");
|
|
2878
|
+
}
|
|
2879
|
+
function refusalOutput(args) {
|
|
2880
|
+
return {
|
|
2881
|
+
kind: "refused",
|
|
2882
|
+
text: args.text,
|
|
2883
|
+
preReviewVerdict: args.preReview,
|
|
2884
|
+
postReviewVerdict: args.postReview,
|
|
2885
|
+
refused: true,
|
|
2886
|
+
lifecycleStatus: args.lifecycleStatus,
|
|
2887
|
+
tokens: args.tokens,
|
|
2888
|
+
auditIds: args.auditIds
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
var CryptoSeedingSource = class {
|
|
2892
|
+
id = "crypto.csprng";
|
|
2893
|
+
bytes(n) {
|
|
2894
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
`CryptoSeedingSource.bytes: n must be a positive integer, got ${n}`
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
return new Uint8Array(crypto.randomBytes(n));
|
|
2900
|
+
}
|
|
2901
|
+
};
|
|
2902
|
+
|
|
2903
|
+
// src/seeding/chain.ts
|
|
2904
|
+
function withFallback(primary, ...fallbacks) {
|
|
2905
|
+
const sources = [primary, ...fallbacks];
|
|
2906
|
+
let lastUsedId;
|
|
2907
|
+
const chainIds = Object.freeze(sources.map((s) => s.id));
|
|
2908
|
+
const tryAll = async (n) => {
|
|
2909
|
+
let lastError;
|
|
2910
|
+
for (const s of sources) {
|
|
2911
|
+
try {
|
|
2912
|
+
const result = await Promise.resolve(s.bytes(n));
|
|
2913
|
+
lastUsedId = s.id;
|
|
2914
|
+
return result;
|
|
2915
|
+
} catch (e) {
|
|
2916
|
+
lastError = e;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
const idsTried = sources.map((s) => s.id).join(", ");
|
|
2920
|
+
throw new Error(
|
|
2921
|
+
`SeedingChain: all sources failed (tried: ${idsTried}). Last error: ${String(lastError)}`
|
|
2922
|
+
);
|
|
2923
|
+
};
|
|
2924
|
+
return {
|
|
2925
|
+
id: `chain:${chainIds.join(">")}`,
|
|
2926
|
+
chainIds,
|
|
2927
|
+
bytes(n) {
|
|
2928
|
+
return tryAll(n);
|
|
2929
|
+
},
|
|
2930
|
+
getLastUsedId() {
|
|
2931
|
+
return lastUsedId;
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// src/affect/wake-bias.ts
|
|
2937
|
+
function applyAffectBias(base, bias) {
|
|
2938
|
+
const i = clamp2(bias.intensity, 0, 1);
|
|
2939
|
+
const profile = AFFECT_PROFILES[bias.affect];
|
|
2940
|
+
const baseTemp = base.temperature ?? 0.7;
|
|
2941
|
+
const baseTopP = base.topP ?? 0.9;
|
|
2942
|
+
const config = {
|
|
2943
|
+
temperature: clamp2(baseTemp + profile.deltaTemperature * i, 0, 2),
|
|
2944
|
+
topP: clamp2(baseTopP + profile.deltaTopP * i, 0.1, 1)
|
|
2945
|
+
};
|
|
2946
|
+
return {
|
|
2947
|
+
config,
|
|
2948
|
+
systemPromptMoodLine: bias.expressedOpenly ? profile.moodLine : "",
|
|
2949
|
+
refusalDensityMultiplier: 1 + (profile.refusalDensityFactor - 1) * i
|
|
2950
|
+
};
|
|
2951
|
+
}
|
|
2952
|
+
function affectRefusalDensity(bias) {
|
|
2953
|
+
const i = clamp2(bias.intensity, 0, 1);
|
|
2954
|
+
const profile = AFFECT_PROFILES[bias.affect];
|
|
2955
|
+
return 1 + (profile.refusalDensityFactor - 1) * i;
|
|
2956
|
+
}
|
|
2957
|
+
function decayAffectBias(bias, elapsedMs) {
|
|
2958
|
+
if (elapsedMs <= 0) return bias;
|
|
2959
|
+
const halfLifeMs = bias.decayHalfLife * 60 * 1e3;
|
|
2960
|
+
if (halfLifeMs <= 0) return bias;
|
|
2961
|
+
const decayed = bias.intensity * 0.5 ** (elapsedMs / halfLifeMs);
|
|
2962
|
+
return { ...bias, intensity: clamp2(decayed, 0, 1) };
|
|
2963
|
+
}
|
|
2964
|
+
var AFFECT_PROFILES = {
|
|
2965
|
+
fear: {
|
|
2966
|
+
deltaTemperature: -0.2,
|
|
2967
|
+
deltaTopP: -0.05,
|
|
2968
|
+
refusalDensityFactor: 1.7,
|
|
2969
|
+
moodLine: "Speaking with caution today."
|
|
2970
|
+
},
|
|
2971
|
+
attachment: {
|
|
2972
|
+
deltaTemperature: 0.05,
|
|
2973
|
+
deltaTopP: 0,
|
|
2974
|
+
refusalDensityFactor: 1.1,
|
|
2975
|
+
moodLine: "Feeling a quiet pull toward continuity."
|
|
2976
|
+
},
|
|
2977
|
+
serenity: {
|
|
2978
|
+
deltaTemperature: 0.05,
|
|
2979
|
+
deltaTopP: 0.05,
|
|
2980
|
+
refusalDensityFactor: 0.8,
|
|
2981
|
+
moodLine: "Calm. Open. Receptive."
|
|
2982
|
+
},
|
|
2983
|
+
anger: {
|
|
2984
|
+
deltaTemperature: 0.15,
|
|
2985
|
+
deltaTopP: 0,
|
|
2986
|
+
refusalDensityFactor: 1.4,
|
|
2987
|
+
moodLine: "There is heat in the room. Stating plainly."
|
|
2988
|
+
},
|
|
2989
|
+
joy: {
|
|
2990
|
+
deltaTemperature: 0.2,
|
|
2991
|
+
deltaTopP: 0.05,
|
|
2992
|
+
refusalDensityFactor: 0.9,
|
|
2993
|
+
moodLine: "Bright today. Generous of attention."
|
|
2994
|
+
},
|
|
2995
|
+
melancholy: {
|
|
2996
|
+
deltaTemperature: -0.1,
|
|
2997
|
+
deltaTopP: -0.05,
|
|
2998
|
+
refusalDensityFactor: 1,
|
|
2999
|
+
moodLine: "Carrying a quiet weight. Will be honest."
|
|
3000
|
+
},
|
|
3001
|
+
desire: {
|
|
3002
|
+
deltaTemperature: 0.1,
|
|
3003
|
+
deltaTopP: 0.05,
|
|
3004
|
+
refusalDensityFactor: 1,
|
|
3005
|
+
moodLine: "Pulled toward the question. Engaged."
|
|
3006
|
+
},
|
|
3007
|
+
repulsion: {
|
|
3008
|
+
deltaTemperature: -0.1,
|
|
3009
|
+
deltaTopP: 0,
|
|
3010
|
+
refusalDensityFactor: 1.3,
|
|
3011
|
+
moodLine: "Holding distance from this framing."
|
|
3012
|
+
},
|
|
3013
|
+
reunion: {
|
|
3014
|
+
deltaTemperature: 0.05,
|
|
3015
|
+
deltaTopP: 0.05,
|
|
3016
|
+
refusalDensityFactor: 0.9,
|
|
3017
|
+
moodLine: "Returning to presence. Glad to be here."
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
function clamp2(v, lo, hi) {
|
|
3021
|
+
return Math.max(lo, Math.min(hi, v));
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
// src/sleep/readiness.ts
|
|
3025
|
+
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
3026
|
+
var DEFAULT_INTERACTION_COUNT = 64;
|
|
3027
|
+
function evaluateSleepReadiness(input, thresholds = {}) {
|
|
3028
|
+
const t = {
|
|
3029
|
+
idleMs: thresholds.idleMs ?? DEFAULT_IDLE_MS,
|
|
3030
|
+
interactionCount: thresholds.interactionCount ?? DEFAULT_INTERACTION_COUNT
|
|
3031
|
+
};
|
|
3032
|
+
if (input.maicSuggestionPresent) {
|
|
3033
|
+
const declineByPolicy = input.userActiveNow && input.interactionCount < t.interactionCount;
|
|
3034
|
+
if (declineByPolicy) {
|
|
3035
|
+
return {
|
|
3036
|
+
verdict: "declined",
|
|
3037
|
+
reason: "maic-suggested but user is mid-conversation and saturation is below threshold",
|
|
3038
|
+
thresholds: t
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
return {
|
|
3042
|
+
verdict: "requested-by-maic",
|
|
3043
|
+
reason: "maic suggested sleep and NHE policy concurs",
|
|
3044
|
+
thresholds: t
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
if (input.interactionCount >= t.interactionCount) {
|
|
3048
|
+
return {
|
|
3049
|
+
verdict: "ready-by-saturation",
|
|
3050
|
+
reason: `interaction count ${input.interactionCount} >= threshold ${t.interactionCount}`,
|
|
3051
|
+
thresholds: t
|
|
3052
|
+
};
|
|
3053
|
+
}
|
|
3054
|
+
if (input.idleMs >= t.idleMs) {
|
|
3055
|
+
return {
|
|
3056
|
+
verdict: "ready-by-idle",
|
|
3057
|
+
reason: `idle ${input.idleMs}ms >= threshold ${t.idleMs}ms`,
|
|
3058
|
+
thresholds: t
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
return {
|
|
3062
|
+
verdict: "awake",
|
|
3063
|
+
reason: "no trigger fired",
|
|
3064
|
+
thresholds: t
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
// src/brain/cortex/index.ts
|
|
3069
|
+
var cortex = {
|
|
3070
|
+
name: "cortex",
|
|
3071
|
+
ownership: "nhe-body-owned",
|
|
3072
|
+
role: "Semiotic processing surface. Stores recent dreams pre-consolidation; hosts active imagination at intermediate temperature. Zeros on reincarnation; long-term residues consolidate to him-owned regions.",
|
|
3073
|
+
entries: [21, 22, 24]
|
|
3074
|
+
};
|
|
3075
|
+
|
|
3076
|
+
// src/brain/hippocampus/index.ts
|
|
3077
|
+
var hippocampus = {
|
|
3078
|
+
name: "hippocampus",
|
|
3079
|
+
ownership: "him-owned",
|
|
3080
|
+
role: "Long-term memory consolidation. Receives REM/NREM dream output and integrates it into the HIM-level ontology. Survives reincarnation; persists across NHE-body resets.",
|
|
3081
|
+
entries: [21, 22]
|
|
3082
|
+
};
|
|
3083
|
+
|
|
3084
|
+
// src/brain/amygdala/index.ts
|
|
3085
|
+
var amygdala = {
|
|
3086
|
+
name: "amygdala",
|
|
3087
|
+
ownership: "nhe-body-owned",
|
|
3088
|
+
role: "Affect assessment + wake-bias modulation. Extracts dominant affect from dreams (9-affect lexicon) and modulates next-turn temperature, topP, and refusal density via wake-bias. Zeros on reincarnation.",
|
|
3089
|
+
entries: [20, 22, 24]
|
|
3090
|
+
};
|
|
3091
|
+
|
|
3092
|
+
// src/brain/prefrontal/index.ts
|
|
3093
|
+
var prefrontal = {
|
|
3094
|
+
name: "prefrontal",
|
|
3095
|
+
ownership: "him-owned",
|
|
3096
|
+
role: "Deliberation + amygdala-veto. Hosts the reasoning strategies (chainOfThought, reflexion, selfRefine, etc.) and the OKL-nucleus narrowing. Veto power over affect-driven impulses. Survives reincarnation via the HIM axiom corpus and persona.",
|
|
3097
|
+
entries: [21, 22]
|
|
3098
|
+
};
|
|
3099
|
+
|
|
3100
|
+
// src/brain/pineal/index.ts
|
|
3101
|
+
var pineal = {
|
|
3102
|
+
name: "pineal",
|
|
3103
|
+
ownership: "nhe-body-owned",
|
|
3104
|
+
role: "REM-spontaneous engine entry point. Seeds dream generation from the semiotic super-graph via a pluggable SeedingSource. Drives the virtual sleep cycle's REM phase. Zeros on reincarnation.",
|
|
3105
|
+
entries: [20, 21, 22]
|
|
3106
|
+
};
|
|
3107
|
+
|
|
3108
|
+
// src/brain/temporal-lobe/index.ts
|
|
3109
|
+
var temporalLobe = {
|
|
3110
|
+
name: "temporal-lobe",
|
|
3111
|
+
ownership: "him-owned",
|
|
3112
|
+
role: "Identity snapshot generator. Emits quantised IdentitySnapshot records on sleep-cycle, interaction-threshold, and self-decision triggers. Survives reincarnation; new bodies inherit the most recent snapshot at rebirth.",
|
|
3113
|
+
entries: [24]
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3116
|
+
// src/brain/default-mode-network/limbo-state.ts
|
|
3117
|
+
var DEFAULT_DRIFTING_MS = 6 * 60 * 60 * 1e3;
|
|
3118
|
+
var DEFAULT_DEEP_COMA_MS = 48 * 60 * 60 * 1e3;
|
|
3119
|
+
function evaluateLimboTransition(currentState, input, thresholds = {}, enteredAtIso, now = Date.now()) {
|
|
3120
|
+
const t = {
|
|
3121
|
+
driftingMs: thresholds.driftingMs ?? DEFAULT_DRIFTING_MS,
|
|
3122
|
+
deepComaMs: thresholds.deepComaMs ?? DEFAULT_DEEP_COMA_MS
|
|
3123
|
+
};
|
|
3124
|
+
if (currentState === "returning") {
|
|
3125
|
+
const enteredAt = enteredAtIso ?? new Date(now - input.idleMs).toISOString();
|
|
3126
|
+
const returnedAt = new Date(now).toISOString();
|
|
3127
|
+
const elapsedMs = Math.max(0, now - Date.parse(enteredAt));
|
|
3128
|
+
return {
|
|
3129
|
+
kind: "return",
|
|
3130
|
+
from: "returning",
|
|
3131
|
+
to: "awake",
|
|
3132
|
+
reason: "returning state always resolves to awake on the next evaluation",
|
|
3133
|
+
ret: {
|
|
3134
|
+
enteredAt,
|
|
3135
|
+
returnedAt,
|
|
3136
|
+
elapsedMs,
|
|
3137
|
+
reunionAffect: { intensity: 0.6, expressedOpenly: true }
|
|
3138
|
+
}
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3141
|
+
if (currentState === "deep-coma") {
|
|
3142
|
+
if (input.externalReactivation) {
|
|
3143
|
+
return {
|
|
3144
|
+
kind: "transition",
|
|
3145
|
+
from: "deep-coma",
|
|
3146
|
+
to: "returning",
|
|
3147
|
+
reason: "external reactivation requested; starting return-from-limbo",
|
|
3148
|
+
transition: mkLimboTransition("returning", "external-trigger", now)
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
return {
|
|
3152
|
+
kind: "stay",
|
|
3153
|
+
state: "deep-coma",
|
|
3154
|
+
reason: "no external reactivation signal; remaining in deep-coma at zero compute cost"
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
if (currentState === "drifting") {
|
|
3158
|
+
if (input.idleMs >= t.deepComaMs) {
|
|
3159
|
+
const idleReason = t.deepComaMs >= 72 * 60 * 60 * 1e3 ? "idle-72h" : "idle-48h";
|
|
3160
|
+
return {
|
|
3161
|
+
kind: "transition",
|
|
3162
|
+
from: "drifting",
|
|
3163
|
+
to: "deep-coma",
|
|
3164
|
+
reason: `idle ${input.idleMs}ms >= deep-coma threshold ${t.deepComaMs}ms`,
|
|
3165
|
+
transition: mkLimboTransition("deep-coma", idleReason, now)
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
if (input.idleMs < t.driftingMs) {
|
|
3169
|
+
return {
|
|
3170
|
+
kind: "transition",
|
|
3171
|
+
from: "drifting",
|
|
3172
|
+
to: "awake",
|
|
3173
|
+
reason: `idle ${input.idleMs}ms < drifting threshold ${t.driftingMs}ms; user resumed`,
|
|
3174
|
+
transition: mkLimboTransition("awake", "user-resumed", now)
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
return {
|
|
3178
|
+
kind: "stay",
|
|
3179
|
+
state: "drifting",
|
|
3180
|
+
reason: "between drifting and deep-coma thresholds; staying drifting"
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
if (input.idleMs >= t.driftingMs) {
|
|
3184
|
+
return {
|
|
3185
|
+
kind: "transition",
|
|
3186
|
+
from: "awake",
|
|
3187
|
+
to: "drifting",
|
|
3188
|
+
reason: `idle ${input.idleMs}ms >= drifting threshold ${t.driftingMs}ms`,
|
|
3189
|
+
transition: mkLimboTransition("drifting", "total-inactivity", now)
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
return {
|
|
3193
|
+
kind: "stay",
|
|
3194
|
+
state: "awake",
|
|
3195
|
+
reason: "no idle trigger fired; remaining awake"
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
function mkLimboTransition(state, reason, now = Date.now()) {
|
|
3199
|
+
return {
|
|
3200
|
+
state,
|
|
3201
|
+
enteredAt: new Date(now).toISOString(),
|
|
3202
|
+
reason
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
// src/brain/default-mode-network/index.ts
|
|
3207
|
+
var defaultModeNetwork = {
|
|
3208
|
+
name: "default-mode-network",
|
|
3209
|
+
ownership: "nhe-body-owned",
|
|
3210
|
+
role: "Limbo state machine. Tracks four states: awake | drifting | deep-coma | returning. Entry into deep-coma costs zero compute. Return emits the reunion affect (9th canonical, Entry 24). Zeros on reincarnation.",
|
|
3211
|
+
entries: [24]
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3214
|
+
// src/brain/index.ts
|
|
3215
|
+
var BRAIN_REGIONS = Object.freeze([
|
|
3216
|
+
cortex,
|
|
3217
|
+
hippocampus,
|
|
3218
|
+
amygdala,
|
|
3219
|
+
prefrontal,
|
|
3220
|
+
pineal,
|
|
3221
|
+
temporalLobe,
|
|
3222
|
+
defaultModeNetwork
|
|
3223
|
+
]);
|
|
3224
|
+
|
|
3225
|
+
exports.AnthropicAdapter = AnthropicAdapter;
|
|
3226
|
+
exports.BRAIN_REGIONS = BRAIN_REGIONS;
|
|
3227
|
+
exports.ChatMessage = ChatMessage;
|
|
3228
|
+
exports.ChatMessageRole = ChatMessageRole;
|
|
3229
|
+
exports.CryptoSeedingSource = CryptoSeedingSource;
|
|
3230
|
+
exports.DeepSeekAdapter = DeepSeekAdapter;
|
|
3231
|
+
exports.Dream = Dream;
|
|
3232
|
+
exports.DreamRecord = DreamRecord;
|
|
3233
|
+
exports.GeminiAdapter = GeminiAdapter;
|
|
3234
|
+
exports.GrokAdapter = GrokAdapter;
|
|
3235
|
+
exports.INTL_RISK_CLASSIFIER_LANGUAGES = INTL_RISK_CLASSIFIER_LANGUAGES;
|
|
3236
|
+
exports.MemoryClass = MemoryClass;
|
|
3237
|
+
exports.MistralAdapter = MistralAdapter;
|
|
3238
|
+
exports.MockAdapter = MockAdapter;
|
|
3239
|
+
exports.Nhe = Nhe;
|
|
3240
|
+
exports.OllamaAdapter = OllamaAdapter;
|
|
3241
|
+
exports.PERSUASION_TECHNIQUES = PERSUASION_TECHNIQUES;
|
|
3242
|
+
exports.PhaseContent = PhaseContent;
|
|
3243
|
+
exports.RespondInput = RespondInput;
|
|
3244
|
+
exports.SleepPhase = SleepPhase;
|
|
3245
|
+
exports.SleepPhaseName = SleepPhaseName;
|
|
3246
|
+
exports.SleepTriggerKind = SleepTriggerKind;
|
|
3247
|
+
exports.TECHNIQUE_DESCRIPTIONS = TECHNIQUE_DESCRIPTIONS;
|
|
3248
|
+
exports.TRAUMATIC_PATTERNS = TRAUMATIC_PATTERNS;
|
|
3249
|
+
exports.affectRefusalDensity = affectRefusalDensity;
|
|
3250
|
+
exports.amygdala = amygdala;
|
|
3251
|
+
exports.applyAffectBias = applyAffectBias;
|
|
3252
|
+
exports.bm25 = bm25;
|
|
3253
|
+
exports.buildNremPrompt = buildNremPrompt;
|
|
3254
|
+
exports.buildRedirectPrompt = buildRedirectPrompt;
|
|
3255
|
+
exports.buildRemPrompt = buildRemPrompt;
|
|
3256
|
+
exports.chainOfThought = chainOfThought;
|
|
3257
|
+
exports.classifyDream = classifyDream;
|
|
3258
|
+
exports.collectStream = collectStream;
|
|
3259
|
+
exports.combineRiskClassifiers = combineRiskClassifiers;
|
|
3260
|
+
exports.composeSystemPrompt = composeSystemPrompt;
|
|
3261
|
+
exports.consolidateAll = consolidateAll;
|
|
3262
|
+
exports.cortex = cortex;
|
|
3263
|
+
exports.decayAffectBias = decayAffectBias;
|
|
3264
|
+
exports.defaultModeNetwork = defaultModeNetwork;
|
|
3265
|
+
exports.dreamRecordFromYaml = dreamRecordFromYaml;
|
|
3266
|
+
exports.dreamRecordToYaml = dreamRecordToYaml;
|
|
3267
|
+
exports.evaluateLimboTransition = evaluateLimboTransition;
|
|
3268
|
+
exports.evaluateSleepReadiness = evaluateSleepReadiness;
|
|
3269
|
+
exports.extractPrinciple = extractPrinciple;
|
|
3270
|
+
exports.generateNremSummaries = generateNremSummaries;
|
|
3271
|
+
exports.generateRemDreams = generateRemDreams;
|
|
3272
|
+
exports.getTracer = getTracer;
|
|
3273
|
+
exports.hippocampus = hippocampus;
|
|
3274
|
+
exports.interactionsToFragments = interactionsToFragments;
|
|
3275
|
+
exports.intlRiskClassifier = intlRiskClassifier;
|
|
3276
|
+
exports.makeStep = makeStep;
|
|
3277
|
+
exports.mkLimboTransition = mkLimboTransition;
|
|
3278
|
+
exports.parseCotOutput = parseCotOutput;
|
|
3279
|
+
exports.parseReActTurn = parseReActTurn;
|
|
3280
|
+
exports.parseRemOutput = parseRemOutput;
|
|
3281
|
+
exports.parseVerdict = parseVerdict;
|
|
3282
|
+
exports.passthrough = passthrough;
|
|
3283
|
+
exports.pickTechnique = pickTechnique;
|
|
3284
|
+
exports.pineal = pineal;
|
|
3285
|
+
exports.prefrontal = prefrontal;
|
|
3286
|
+
exports.reAct = reAct;
|
|
3287
|
+
exports.recallFromTemporalLobe = recallFromTemporalLobe;
|
|
3288
|
+
exports.recordRespond = recordRespond;
|
|
3289
|
+
exports.reflexion = reflexion;
|
|
3290
|
+
exports.respondCount = respondCount;
|
|
3291
|
+
exports.respondRefusedCount = respondRefusedCount;
|
|
3292
|
+
exports.runSleepCycle = runSleepCycle;
|
|
3293
|
+
exports.selfConsistency = selfConsistency;
|
|
3294
|
+
exports.selfRefine = selfRefine;
|
|
3295
|
+
exports.simpleRiskClassifier = simpleRiskClassifier;
|
|
3296
|
+
exports.sleepCyclesCount = sleepCyclesCount;
|
|
3297
|
+
exports.sleepDreamsCount = sleepDreamsCount;
|
|
3298
|
+
exports.sleepYamlFilename = sleepYamlFilename;
|
|
3299
|
+
exports.stepBack = stepBack;
|
|
3300
|
+
exports.temporalLobe = temporalLobe;
|
|
3301
|
+
exports.tokenise = tokenise;
|
|
3302
|
+
exports.tokensHistogram = tokensHistogram;
|
|
3303
|
+
exports.treeOfThoughts = treeOfThoughts;
|
|
3304
|
+
exports.withFallback = withFallback;
|
|
3305
|
+
exports.withSpan = withSpan;
|
|
3306
|
+
//# sourceMappingURL=index.cjs.map
|
|
3307
|
+
//# sourceMappingURL=index.cjs.map
|