emocentric 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/dist/actions/action.d.ts +70 -0
  4. package/dist/actions/action.js +47 -0
  5. package/dist/actions/index.d.ts +2 -0
  6. package/dist/actions/index.js +2 -0
  7. package/dist/actions/registry.d.ts +12 -0
  8. package/dist/actions/registry.js +26 -0
  9. package/dist/agent.d.ts +141 -0
  10. package/dist/agent.js +246 -0
  11. package/dist/blueprint/blueprint.d.ts +60 -0
  12. package/dist/blueprint/blueprint.js +20 -0
  13. package/dist/blueprint/index.d.ts +2 -0
  14. package/dist/blueprint/index.js +2 -0
  15. package/dist/blueprint/scope.d.ts +26 -0
  16. package/dist/blueprint/scope.js +21 -0
  17. package/dist/emotion/definitions.d.ts +34 -0
  18. package/dist/emotion/definitions.js +43 -0
  19. package/dist/emotion/index.d.ts +3 -0
  20. package/dist/emotion/index.js +3 -0
  21. package/dist/emotion/persona.d.ts +35 -0
  22. package/dist/emotion/persona.js +22 -0
  23. package/dist/emotion/state.d.ts +52 -0
  24. package/dist/emotion/state.js +35 -0
  25. package/dist/events/context-event.d.ts +49 -0
  26. package/dist/events/context-event.js +43 -0
  27. package/dist/events/guards.d.ts +14 -0
  28. package/dist/events/guards.js +72 -0
  29. package/dist/events/index.d.ts +8 -0
  30. package/dist/events/index.js +5 -0
  31. package/dist/events/media.d.ts +23 -0
  32. package/dist/events/media.js +16 -0
  33. package/dist/events/merge.d.ts +24 -0
  34. package/dist/events/merge.js +61 -0
  35. package/dist/events/payload.d.ts +54 -0
  36. package/dist/events/payload.js +8 -0
  37. package/dist/gateway/client.d.ts +28 -0
  38. package/dist/gateway/client.js +79 -0
  39. package/dist/gateway/index.d.ts +1 -0
  40. package/dist/gateway/index.js +1 -0
  41. package/dist/index.d.ts +9 -0
  42. package/dist/index.js +13 -0
  43. package/dist/llm/index.d.ts +1 -0
  44. package/dist/llm/index.js +1 -0
  45. package/dist/llm/types.d.ts +40 -0
  46. package/dist/llm/types.js +1 -0
  47. package/dist/memory/fact.d.ts +54 -0
  48. package/dist/memory/fact.js +1 -0
  49. package/dist/memory/index.d.ts +4 -0
  50. package/dist/memory/index.js +2 -0
  51. package/dist/memory/openai-embedder.d.ts +50 -0
  52. package/dist/memory/openai-embedder.js +124 -0
  53. package/dist/memory/scoring.d.ts +24 -0
  54. package/dist/memory/scoring.js +20 -0
  55. package/dist/memory/store.d.ts +79 -0
  56. package/dist/memory/store.js +1 -0
  57. package/dist/openrouter/client.d.ts +27 -0
  58. package/dist/openrouter/client.js +118 -0
  59. package/dist/openrouter/index.d.ts +1 -0
  60. package/dist/openrouter/index.js +1 -0
  61. package/dist/pipeline/actor.d.ts +31 -0
  62. package/dist/pipeline/actor.js +11 -0
  63. package/dist/pipeline/appraiser.d.ts +55 -0
  64. package/dist/pipeline/appraiser.js +132 -0
  65. package/dist/pipeline/consolidator.d.ts +62 -0
  66. package/dist/pipeline/consolidator.js +98 -0
  67. package/dist/pipeline/inbox.d.ts +60 -0
  68. package/dist/pipeline/inbox.js +107 -0
  69. package/dist/pipeline/index.d.ts +11 -0
  70. package/dist/pipeline/index.js +11 -0
  71. package/dist/pipeline/interpretation.d.ts +47 -0
  72. package/dist/pipeline/interpretation.js +24 -0
  73. package/dist/pipeline/llm-interpreter.d.ts +28 -0
  74. package/dist/pipeline/llm-interpreter.js +173 -0
  75. package/dist/pipeline/loop.d.ts +58 -0
  76. package/dist/pipeline/loop.js +73 -0
  77. package/dist/pipeline/processor.d.ts +63 -0
  78. package/dist/pipeline/processor.js +144 -0
  79. package/dist/pipeline/recaller.d.ts +34 -0
  80. package/dist/pipeline/recaller.js +38 -0
  81. package/dist/pipeline/time-appraiser.d.ts +85 -0
  82. package/dist/pipeline/time-appraiser.js +189 -0
  83. package/dist/pipeline/writer.d.ts +113 -0
  84. package/dist/pipeline/writer.js +210 -0
  85. package/dist/postgres/create-instance.d.ts +107 -0
  86. package/dist/postgres/create-instance.js +182 -0
  87. package/dist/postgres/emotional-state.d.ts +42 -0
  88. package/dist/postgres/emotional-state.js +134 -0
  89. package/dist/postgres/index.d.ts +5 -0
  90. package/dist/postgres/index.js +5 -0
  91. package/dist/postgres/instances.d.ts +32 -0
  92. package/dist/postgres/instances.js +77 -0
  93. package/dist/postgres/schema.d.ts +31 -0
  94. package/dist/postgres/schema.js +98 -0
  95. package/dist/postgres/short-term.d.ts +32 -0
  96. package/dist/postgres/short-term.js +95 -0
  97. package/dist/postgres/store.d.ts +85 -0
  98. package/dist/postgres/store.js +347 -0
  99. package/dist/short-term/index.d.ts +1 -0
  100. package/dist/short-term/index.js +1 -0
  101. package/dist/short-term/short-term.d.ts +66 -0
  102. package/dist/short-term/short-term.js +72 -0
  103. package/package.json +50 -0
@@ -0,0 +1,49 @@
1
+ import type { Payload } from "./payload.js";
2
+ /** Who caused the event. Usually the human user, but adapters may also
3
+ * emit events for other entities (another player, a system timer). */
4
+ export interface Actor {
5
+ /** Stable identifier within the platform (user id, gamertag, handle). */
6
+ id: string;
7
+ displayName?: string;
8
+ metadata?: Record<string, unknown>;
9
+ }
10
+ /**
11
+ * The normalized envelope every trigger produces. This is the single input
12
+ * type the rest of the pipeline (interpreter, appraiser, processor, writer)
13
+ * consumes — platform adapters translate native events into this shape.
14
+ *
15
+ * All fields except `raw` are JSON-serializable (when the payload avoids
16
+ * `bytes` media sources), so events can cross process boundaries.
17
+ *
18
+ * @typeParam TRaw - the platform-native event type, kept for adapters and
19
+ * actions that need platform-specific details (e.g. a reply token).
20
+ */
21
+ export interface ContextEvent<TRaw = unknown> {
22
+ /** Unique id for this event (UUID v4 by default). */
23
+ id: string;
24
+ /** When the event happened, ISO 8601. */
25
+ occurredAt: string;
26
+ /** Which platform produced it: "discord", "imessage", "unity", ... */
27
+ platform: string;
28
+ /** Conversation/scene scope within the platform (channel, thread, level). */
29
+ channelId?: string;
30
+ actor: Actor;
31
+ payload: Payload;
32
+ /** The original platform-native event, untouched. */
33
+ raw?: TRaw;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+ /** Input to {@link createContextEvent}: `id` and `occurredAt` are filled in. */
37
+ export type ContextEventInput<TRaw = unknown> = Omit<ContextEvent<TRaw>, "id" | "occurredAt"> & Partial<Pick<ContextEvent<TRaw>, "id" | "occurredAt">>;
38
+ /**
39
+ * Build a ContextEvent, generating `id` and `occurredAt` when absent.
40
+ * Adapters should prefer this over hand-assembling the object so ids and
41
+ * timestamps stay consistent across platforms.
42
+ */
43
+ export declare function createContextEvent<TRaw = unknown>(input: ContextEventInput<TRaw>): ContextEvent<TRaw>;
44
+ /**
45
+ * UUID v4, runtime-portable: Web Crypto where available (Node 19+, browsers,
46
+ * Deno, Bun), with a Math.random fallback for embedded JS runtimes that lack
47
+ * `crypto` (some game-engine script hosts).
48
+ */
49
+ export declare function generateEventId(): string;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Build a ContextEvent, generating `id` and `occurredAt` when absent.
3
+ * Adapters should prefer this over hand-assembling the object so ids and
4
+ * timestamps stay consistent across platforms.
5
+ */
6
+ export function createContextEvent(input) {
7
+ return {
8
+ ...input,
9
+ id: input.id ?? generateEventId(),
10
+ occurredAt: input.occurredAt ?? new Date().toISOString(),
11
+ };
12
+ }
13
+ /**
14
+ * UUID v4, runtime-portable: Web Crypto where available (Node 19+, browsers,
15
+ * Deno, Bun), with a Math.random fallback for embedded JS runtimes that lack
16
+ * `crypto` (some game-engine script hosts).
17
+ */
18
+ export function generateEventId() {
19
+ const cryptoApi = globalThis.crypto;
20
+ if (cryptoApi?.randomUUID)
21
+ return cryptoApi.randomUUID();
22
+ const bytes = new Uint8Array(16);
23
+ if (cryptoApi?.getRandomValues) {
24
+ cryptoApi.getRandomValues(bytes);
25
+ }
26
+ else {
27
+ for (let i = 0; i < 16; i++)
28
+ bytes[i] = Math.floor(Math.random() * 256);
29
+ }
30
+ // RFC 4122 version and variant bits
31
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
32
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
33
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
34
+ return (hex.slice(0, 4).join("") +
35
+ "-" +
36
+ hex.slice(4, 6).join("") +
37
+ "-" +
38
+ hex.slice(6, 8).join("") +
39
+ "-" +
40
+ hex.slice(8, 10).join("") +
41
+ "-" +
42
+ hex.slice(10, 16).join(""));
43
+ }
@@ -0,0 +1,14 @@
1
+ import type { Payload, SimplePayload } from "./payload.js";
2
+ import type { Actor, ContextEvent } from "./context-event.js";
3
+ /** Runtime check for a non-composite payload. */
4
+ export declare function isSimplePayload(value: unknown): value is SimplePayload;
5
+ /** Runtime check for any payload, rejecting nested composites. */
6
+ export declare function isPayload(value: unknown): value is Payload;
7
+ export declare function isActor(value: unknown): value is Actor;
8
+ /**
9
+ * Runtime check for a ContextEvent — use at trust boundaries (deserializing
10
+ * from a queue, accepting events from third-party adapters).
11
+ */
12
+ export declare function isContextEvent(value: unknown): value is ContextEvent;
13
+ /** Throwing variant of {@link isContextEvent} with a descriptive error. */
14
+ export declare function assertContextEvent(value: unknown): asserts value is ContextEvent;
@@ -0,0 +1,72 @@
1
+ import { isMediaSource } from "./media.js";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ /** Runtime check for a non-composite payload. */
6
+ export function isSimplePayload(value) {
7
+ if (!isRecord(value))
8
+ return false;
9
+ switch (value.kind) {
10
+ case "text":
11
+ return typeof value.text === "string";
12
+ case "image":
13
+ return isMediaSource(value.source);
14
+ case "audio":
15
+ case "video":
16
+ return isMediaSource(value.source);
17
+ case "action":
18
+ return (typeof value.name === "string" &&
19
+ (value.data === undefined || isRecord(value.data)));
20
+ default:
21
+ return false;
22
+ }
23
+ }
24
+ /** Runtime check for any payload, rejecting nested composites. */
25
+ export function isPayload(value) {
26
+ if (isSimplePayload(value))
27
+ return true;
28
+ if (!isRecord(value) || value.kind !== "composite")
29
+ return false;
30
+ return (Array.isArray(value.parts) &&
31
+ value.parts.length > 0 &&
32
+ value.parts.every(isSimplePayload));
33
+ }
34
+ export function isActor(value) {
35
+ return isRecord(value) && typeof value.id === "string" && value.id.length > 0;
36
+ }
37
+ /**
38
+ * Runtime check for a ContextEvent — use at trust boundaries (deserializing
39
+ * from a queue, accepting events from third-party adapters).
40
+ */
41
+ export function isContextEvent(value) {
42
+ if (!isRecord(value))
43
+ return false;
44
+ return (typeof value.id === "string" &&
45
+ value.id.length > 0 &&
46
+ typeof value.occurredAt === "string" &&
47
+ !Number.isNaN(Date.parse(value.occurredAt)) &&
48
+ typeof value.platform === "string" &&
49
+ value.platform.length > 0 &&
50
+ (value.channelId === undefined || typeof value.channelId === "string") &&
51
+ isActor(value.actor) &&
52
+ isPayload(value.payload));
53
+ }
54
+ /** Throwing variant of {@link isContextEvent} with a descriptive error. */
55
+ export function assertContextEvent(value) {
56
+ if (!isContextEvent(value)) {
57
+ throw new TypeError(`Value is not a valid ContextEvent: ${safePreview(value)}`);
58
+ }
59
+ }
60
+ function safePreview(value) {
61
+ try {
62
+ const json = JSON.stringify(value);
63
+ return json === undefined
64
+ ? String(value)
65
+ : json.length > 200
66
+ ? json.slice(0, 200) + "…"
67
+ : json;
68
+ }
69
+ catch {
70
+ return Object.prototype.toString.call(value);
71
+ }
72
+ }
@@ -0,0 +1,8 @@
1
+ export type { MediaSource } from "./media.js";
2
+ export { isMediaSource } from "./media.js";
3
+ export type { TextPayload, ImagePayload, AudioPayload, VideoPayload, ActionPayload, SimplePayload, CompositePayload, Payload, PayloadKind, } from "./payload.js";
4
+ export { PAYLOAD_KINDS } from "./payload.js";
5
+ export type { Actor, ContextEvent, ContextEventInput, } from "./context-event.js";
6
+ export { createContextEvent, generateEventId } from "./context-event.js";
7
+ export { mergeEvents } from "./merge.js";
8
+ export { isSimplePayload, isPayload, isActor, isContextEvent, assertContextEvent, } from "./guards.js";
@@ -0,0 +1,5 @@
1
+ export { isMediaSource } from "./media.js";
2
+ export { PAYLOAD_KINDS } from "./payload.js";
3
+ export { createContextEvent, generateEventId } from "./context-event.js";
4
+ export { mergeEvents } from "./merge.js";
5
+ export { isSimplePayload, isPayload, isActor, isContextEvent, assertContextEvent, } from "./guards.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A reference to a piece of media (image, audio, video).
3
+ *
4
+ * Three transports are supported so the SDK works in any JS runtime:
5
+ * - `url` — remote media; the consumer fetches it when needed.
6
+ * - `base64` — inline media, JSON-serializable (safe to put on a queue).
7
+ * - `bytes` — inline media as raw bytes, zero-copy within a process.
8
+ */
9
+ export type MediaSource = {
10
+ type: "url";
11
+ url: string;
12
+ mimeType?: string;
13
+ } | {
14
+ type: "base64";
15
+ data: string;
16
+ mimeType: string;
17
+ } | {
18
+ type: "bytes";
19
+ data: Uint8Array;
20
+ mimeType: string;
21
+ };
22
+ /** Narrow a MediaSource by transport. */
23
+ export declare function isMediaSource(value: unknown): value is MediaSource;
@@ -0,0 +1,16 @@
1
+ /** Narrow a MediaSource by transport. */
2
+ export function isMediaSource(value) {
3
+ if (typeof value !== "object" || value === null)
4
+ return false;
5
+ const v = value;
6
+ switch (v.type) {
7
+ case "url":
8
+ return typeof v.url === "string";
9
+ case "base64":
10
+ return typeof v.data === "string" && typeof v.mimeType === "string";
11
+ case "bytes":
12
+ return v.data instanceof Uint8Array && typeof v.mimeType === "string";
13
+ default:
14
+ return false;
15
+ }
16
+ }
@@ -0,0 +1,24 @@
1
+ import { type ContextEvent } from "./context-event.js";
2
+ /**
3
+ * Fuse several events from one actor into a single event the pipeline can
4
+ * process once. This is the mechanical half of event coalescing: when a
5
+ * user fires off a burst ("yes!" then "actually—" then a photo), running
6
+ * three separate appraisals would be wrong and wasteful; merging lets the
7
+ * agent perceive and respond to the whole burst as one moment.
8
+ *
9
+ * - **payloads** flatten and concatenate into one {@link CompositePayload}
10
+ * (each event's parts in order, oldest first), so the interpreter reads
11
+ * them as a sequence; nested composites are flattened, never re-nested.
12
+ * - **occurredAt** is the latest event's time (decay and ordering stay
13
+ * correct).
14
+ * - **actor / platform / channelId / raw** come from the latest event — the
15
+ * freshest reply handle and display name.
16
+ * - **metadata.mergedFrom** keeps every original event (flattened — merging
17
+ * an already-merged event expands its originals rather than nesting), so a
18
+ * reply can still address each source message.
19
+ *
20
+ * One event in → that event back, untouched (nothing to merge). Zero events
21
+ * is a programming error. All events must share an `actor.id`; mixing actors
22
+ * would fuse different people's moments into one and is rejected.
23
+ */
24
+ export declare function mergeEvents(events: ContextEvent[]): ContextEvent;
@@ -0,0 +1,61 @@
1
+ import { generateEventId, } from "./context-event.js";
2
+ /**
3
+ * Fuse several events from one actor into a single event the pipeline can
4
+ * process once. This is the mechanical half of event coalescing: when a
5
+ * user fires off a burst ("yes!" then "actually—" then a photo), running
6
+ * three separate appraisals would be wrong and wasteful; merging lets the
7
+ * agent perceive and respond to the whole burst as one moment.
8
+ *
9
+ * - **payloads** flatten and concatenate into one {@link CompositePayload}
10
+ * (each event's parts in order, oldest first), so the interpreter reads
11
+ * them as a sequence; nested composites are flattened, never re-nested.
12
+ * - **occurredAt** is the latest event's time (decay and ordering stay
13
+ * correct).
14
+ * - **actor / platform / channelId / raw** come from the latest event — the
15
+ * freshest reply handle and display name.
16
+ * - **metadata.mergedFrom** keeps every original event (flattened — merging
17
+ * an already-merged event expands its originals rather than nesting), so a
18
+ * reply can still address each source message.
19
+ *
20
+ * One event in → that event back, untouched (nothing to merge). Zero events
21
+ * is a programming error. All events must share an `actor.id`; mixing actors
22
+ * would fuse different people's moments into one and is rejected.
23
+ */
24
+ export function mergeEvents(events) {
25
+ if (events.length === 0) {
26
+ throw new Error("mergeEvents requires at least one event.");
27
+ }
28
+ if (events.length === 1)
29
+ return events[0];
30
+ const actorId = events[0].actor.id;
31
+ if (!events.every((e) => e.actor.id === actorId)) {
32
+ throw new Error("mergeEvents refuses to merge events from different actors.");
33
+ }
34
+ // Oldest first; Array.sort is stable, so equal timestamps keep input order.
35
+ const ordered = [...events].sort((a, b) => Date.parse(a.occurredAt) - Date.parse(b.occurredAt));
36
+ const latest = ordered[ordered.length - 1];
37
+ const parts = ordered.flatMap((e) => flattenPayload(e.payload));
38
+ const mergedFrom = ordered.flatMap(originalsOf);
39
+ return {
40
+ id: generateEventId(),
41
+ occurredAt: latest.occurredAt,
42
+ platform: latest.platform,
43
+ actor: latest.actor,
44
+ ...(latest.channelId !== undefined && { channelId: latest.channelId }),
45
+ payload: { kind: "composite", parts },
46
+ ...(latest.raw !== undefined && { raw: latest.raw }),
47
+ metadata: { ...(latest.metadata ?? {}), mergedFrom },
48
+ };
49
+ }
50
+ /** A composite's parts, or the single payload wrapped — never nested. */
51
+ function flattenPayload(payload) {
52
+ return payload.kind === "composite" ? payload.parts : [payload];
53
+ }
54
+ /** The true source events behind one event: its own `mergedFrom` if it was
55
+ * already a merge, else itself. Keeps `mergedFrom` a flat list of originals. */
56
+ function originalsOf(event) {
57
+ const prior = event.metadata?.["mergedFrom"];
58
+ if (Array.isArray(prior) && prior.length > 0)
59
+ return prior;
60
+ return [event];
61
+ }
@@ -0,0 +1,54 @@
1
+ import type { MediaSource } from "./media.js";
2
+ /** Plain text — a chat message, a tweet, a caption. */
3
+ export interface TextPayload {
4
+ kind: "text";
5
+ text: string;
6
+ }
7
+ /** A single image, optionally with platform-provided caption/alt text. */
8
+ export interface ImagePayload {
9
+ kind: "image";
10
+ source: MediaSource;
11
+ caption?: string;
12
+ }
13
+ /** Audio, optionally with a platform-provided transcript. */
14
+ export interface AudioPayload {
15
+ kind: "audio";
16
+ source: MediaSource;
17
+ durationMs?: number;
18
+ transcript?: string;
19
+ }
20
+ /** Video, optionally with a platform-provided transcript. */
21
+ export interface VideoPayload {
22
+ kind: "video";
23
+ source: MediaSource;
24
+ durationMs?: number;
25
+ transcript?: string;
26
+ }
27
+ /**
28
+ * A structured, non-media action performed by the user — a game move,
29
+ * a button press, a reaction emoji, an in-app purchase.
30
+ *
31
+ * `name` identifies the action type ("player_attacked", "gift_sent");
32
+ * `description` is an optional human-readable summary the interpreter can
33
+ * use directly; `data` carries the structured details.
34
+ */
35
+ export interface ActionPayload {
36
+ kind: "action";
37
+ name: string;
38
+ description?: string;
39
+ data?: Record<string, unknown>;
40
+ }
41
+ /** Every payload except composite. Composite parts must be one of these. */
42
+ export type SimplePayload = TextPayload | ImagePayload | AudioPayload | VideoPayload | ActionPayload;
43
+ /**
44
+ * A multi-part payload — e.g. a message containing text plus two images.
45
+ * Parts are ordered as the user presented them. Nesting composites is
46
+ * intentionally not representable: one event = one flat list of parts.
47
+ */
48
+ export interface CompositePayload {
49
+ kind: "composite";
50
+ parts: SimplePayload[];
51
+ }
52
+ export type Payload = SimplePayload | CompositePayload;
53
+ export type PayloadKind = Payload["kind"];
54
+ export declare const PAYLOAD_KINDS: readonly PayloadKind[];
@@ -0,0 +1,8 @@
1
+ export const PAYLOAD_KINDS = [
2
+ "text",
3
+ "image",
4
+ "audio",
5
+ "video",
6
+ "action",
7
+ "composite",
8
+ ];
@@ -0,0 +1,28 @@
1
+ import type { LLMClient, LLMRequest, LLMResponse } from "../llm/types.js";
2
+ export interface GatewayLLMClientOptions {
3
+ /** Base URL of the platform's LLM gateway, e.g.
4
+ * "https://emocentric-gateway.up.railway.app". */
5
+ url: string;
6
+ /** The developer's platform token — an opaque badge, not an OpenRouter
7
+ * key. The gateway holds the real key and never sends it back. */
8
+ token: string;
9
+ /** Model id requested from the gateway (subject to its allowlist). */
10
+ model: string;
11
+ /** Abort requests after this many ms. Default 120_000. */
12
+ timeoutMs?: number;
13
+ }
14
+ /**
15
+ * LLMClient that completes through the platform's gateway instead of a
16
+ * provider directly. The developer authenticates with a platform token; the
17
+ * gateway attaches the platform's provider key server-side, so reasoning
18
+ * stages run on the platform's budget without the key ever reaching the
19
+ * developer's machine.
20
+ */
21
+ export declare class GatewayLLMClient implements LLMClient {
22
+ private readonly url;
23
+ private readonly token;
24
+ private readonly model;
25
+ private readonly timeoutMs;
26
+ constructor(options: GatewayLLMClientOptions);
27
+ complete(request: LLMRequest): Promise<LLMResponse>;
28
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * LLMClient that completes through the platform's gateway instead of a
3
+ * provider directly. The developer authenticates with a platform token; the
4
+ * gateway attaches the platform's provider key server-side, so reasoning
5
+ * stages run on the platform's budget without the key ever reaching the
6
+ * developer's machine.
7
+ */
8
+ export class GatewayLLMClient {
9
+ url;
10
+ token;
11
+ model;
12
+ timeoutMs;
13
+ constructor(options) {
14
+ if (!options.url)
15
+ throw new Error("GatewayLLMClient needs a gateway url.");
16
+ if (!options.token)
17
+ throw new Error("GatewayLLMClient needs a platform token.");
18
+ this.url = options.url.replace(/\/+$/, "");
19
+ this.token = options.token;
20
+ this.model = options.model;
21
+ this.timeoutMs = options.timeoutMs ?? 120_000;
22
+ }
23
+ async complete(request) {
24
+ const body = {
25
+ model: this.model,
26
+ messages: request.messages.map(toWireMessage),
27
+ };
28
+ if (request.system !== undefined)
29
+ body.system = request.system;
30
+ if (request.maxTokens !== undefined)
31
+ body.maxTokens = request.maxTokens;
32
+ if (request.jsonSchema !== undefined)
33
+ body.jsonSchema = request.jsonSchema;
34
+ const response = await fetch(`${this.url}/v1/complete`, {
35
+ method: "POST",
36
+ headers: {
37
+ Authorization: `Bearer ${this.token}`,
38
+ "Content-Type": "application/json",
39
+ },
40
+ body: JSON.stringify(body),
41
+ signal: AbortSignal.timeout(this.timeoutMs),
42
+ });
43
+ const payload = (await response.json().catch(() => ({})));
44
+ if (!response.ok) {
45
+ throw new Error(`Gateway request failed (${response.status} ${response.statusText}): ` +
46
+ (payload.error ?? "no error detail"));
47
+ }
48
+ if (typeof payload.text !== "string" || payload.text === "") {
49
+ throw new Error("Gateway returned no completion text.");
50
+ }
51
+ return { text: payload.text };
52
+ }
53
+ }
54
+ /** Bytes sources don't survive JSON (Uint8Array serializes as an object),
55
+ * so they're converted to base64 before crossing the wire. */
56
+ function toWireMessage(message) {
57
+ return { ...message, content: message.content.map(toWireContent) };
58
+ }
59
+ function toWireContent(content) {
60
+ if (content.type !== "image" || content.source.type !== "bytes") {
61
+ return content;
62
+ }
63
+ const source = {
64
+ type: "base64",
65
+ data: bytesToBase64(content.source.data),
66
+ mimeType: content.source.mimeType,
67
+ };
68
+ return { type: "image", source };
69
+ }
70
+ function bytesToBase64(bytes) {
71
+ if (typeof Buffer !== "undefined") {
72
+ return Buffer.from(bytes).toString("base64");
73
+ }
74
+ let binary = "";
75
+ for (let i = 0; i < bytes.length; i += 0x8000) {
76
+ binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
77
+ }
78
+ return btoa(binary);
79
+ }
@@ -0,0 +1 @@
1
+ export { GatewayLLMClient, type GatewayLLMClientOptions } from "./client.js";
@@ -0,0 +1 @@
1
+ export { GatewayLLMClient } from "./client.js";
@@ -0,0 +1,9 @@
1
+ export { Agent, AgentError, type AgentOptions, type AgentResult, type AgentStage, type HandleEventOptions, } from "./agent.js";
2
+ export * from "./blueprint/index.js";
3
+ export * from "./events/index.js";
4
+ export * from "./memory/index.js";
5
+ export * from "./short-term/index.js";
6
+ export * from "./emotion/index.js";
7
+ export * from "./actions/index.js";
8
+ export * from "./llm/index.js";
9
+ export * from "./pipeline/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export { Agent, AgentError, } from "./agent.js";
2
+ export * from "./blueprint/index.js";
3
+ export * from "./events/index.js";
4
+ export * from "./memory/index.js";
5
+ export * from "./short-term/index.js";
6
+ export * from "./emotion/index.js";
7
+ export * from "./actions/index.js";
8
+ export * from "./llm/index.js";
9
+ export * from "./pipeline/index.js";
10
+ // Node-only / provider-specific implementations live behind their own entry
11
+ // points so core imports stay runtime-portable:
12
+ // emocentric/postgres — PostgresMemoryStore
13
+ // emocentric/openrouter — OpenRouterClient
@@ -0,0 +1 @@
1
+ export type { LLMClient, LLMContent, LLMMessage, LLMRequest, LLMResponse, } from "./types.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import type { MediaSource } from "../events/media.js";
2
+ /** One piece of a message: text or an image. (Audio/video reach the LLM as
3
+ * text — transcripts/captions — because chat models don't ingest them raw.) */
4
+ export type LLMContent = {
5
+ type: "text";
6
+ text: string;
7
+ } | {
8
+ type: "image";
9
+ source: MediaSource;
10
+ };
11
+ export interface LLMMessage {
12
+ role: "user" | "assistant";
13
+ content: LLMContent[];
14
+ }
15
+ export interface LLMRequest {
16
+ /** System prompt — who the model is for this call. */
17
+ system?: string;
18
+ messages: LLMMessage[];
19
+ maxTokens?: number;
20
+ /** When set, the client asks the provider for JSON conforming to this
21
+ * schema (providers without schema support get prompt-level enforcement
22
+ * instead, so callers must still parse defensively). */
23
+ jsonSchema?: {
24
+ name: string;
25
+ schema: Record<string, unknown>;
26
+ };
27
+ }
28
+ export interface LLMResponse {
29
+ text: string;
30
+ }
31
+ /**
32
+ * The LLM boundary of the SDK — every model-touching component (interpreter,
33
+ * appraiser, processor, writer) calls this one method and nothing else.
34
+ * The SDK ships an OpenRouter implementation (`emocentric/openrouter`) so
35
+ * developers pick any model by name; bringing a different provider means
36
+ * implementing `complete` and passing your client in.
37
+ */
38
+ export interface LLMClient {
39
+ complete(request: LLMRequest): Promise<LLMResponse>;
40
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ /**
2
+ * What a fact is about. The four built-in kinds come from the agent design:
3
+ * - `persona` — who the agent is (traits, voice, backstory)
4
+ * - `self` — things the agent learned about itself ("I'm bad at chess")
5
+ * - `user` — things about a user ("Alice has a golden retriever")
6
+ * - `event` — specific things that happened ("On 2026-06-01 Alice showed
7
+ * me a photo of her new apartment")
8
+ *
9
+ * The `(string & {})` arm keeps the union open so developers can add their
10
+ * own kinds ("world", "relationship", ...) without losing autocomplete on
11
+ * the built-ins.
12
+ */
13
+ export type FactKind = "persona" | "self" | "user" | "event" | (string & {});
14
+ /** One remembered thing, stored as natural-language text. */
15
+ export interface Fact {
16
+ id: string;
17
+ kind: FactKind;
18
+ /** The fact itself, written as a standalone sentence. The recaller
19
+ * searches this text and the interpreter quotes it verbatim, so it must
20
+ * make sense without surrounding context. */
21
+ content: string;
22
+ /** Who the fact is about, when it's about someone — usually an Actor id. */
23
+ subjectId?: string;
24
+ /** 0..1, how much this fact matters. Feeds into retrieval ranking. */
25
+ importance: number;
26
+ /** ISO 8601. */
27
+ createdAt: string;
28
+ /** ISO 8601, set by the store when the fact is returned from a fetch. */
29
+ lastAccessedAt?: string;
30
+ /** Id of the fact that replaced this one. Superseded facts are kept for
31
+ * history but excluded from retrieval by default. */
32
+ supersededBy?: string;
33
+ metadata?: Record<string, unknown>;
34
+ }
35
+ /** Input to MemoryStore.add — id/createdAt are generated when absent. */
36
+ export interface FactInput {
37
+ kind: FactKind;
38
+ content: string;
39
+ subjectId?: string;
40
+ /** Defaults to 0.5. */
41
+ importance?: number;
42
+ metadata?: Record<string, unknown>;
43
+ id?: string;
44
+ createdAt?: string;
45
+ }
46
+ /** Partial update for MemoryStore.update. */
47
+ export interface FactPatch {
48
+ kind?: FactKind;
49
+ content?: string;
50
+ subjectId?: string;
51
+ importance?: number;
52
+ supersededBy?: string;
53
+ metadata?: Record<string, unknown>;
54
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ export type { Fact, FactInput, FactKind, FactPatch } from "./fact.js";
2
+ export type { Embedder, MemoryQuery, MemoryStore, ScoredFact, } from "./store.js";
3
+ export { DEFAULT_SCORING_WEIGHTS, type ScoringWeights, blendScore, clamp01, recencyScore, } from "./scoring.js";
4
+ export { OpenAIEmbedder, type OpenAIEmbedderOptions } from "./openai-embedder.js";
@@ -0,0 +1,2 @@
1
+ export { DEFAULT_SCORING_WEIGHTS, blendScore, clamp01, recencyScore, } from "./scoring.js";
2
+ export { OpenAIEmbedder } from "./openai-embedder.js";