cognia-sdk 0.0.0-stage → 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.
- package/README.md +46 -2
- package/dist/cjs/client.js +90 -0
- package/dist/cjs/index.js +16 -0
- package/dist/cjs/middleware.js +125 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/render.js +33 -0
- package/dist/cjs/session.js +34 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/client.d.ts +50 -0
- package/dist/esm/client.js +85 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/middleware.d.ts +48 -0
- package/dist/esm/middleware.js +119 -0
- package/dist/esm/render.d.ts +5 -0
- package/dist/esm/render.js +29 -0
- package/dist/esm/session.d.ts +26 -0
- package/dist/esm/session.js +30 -0
- package/dist/esm/types.d.ts +117 -0
- package/dist/esm/types.js +1 -0
- package/package.json +48 -3
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cognia-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cognia for your model calls. One line, and every request your agent makes gets the Memories and Skills its identity
|
|
4
|
+
may use, with provenance the model can cite. It fails open: if Cognia is slow or down, the model call proceeds without
|
|
5
|
+
context.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Cognia, withCognia, CogniaSession } from "cognia-sdk";
|
|
9
|
+
|
|
10
|
+
const cognia = new Cognia({ apiKey: process.env.COGNIA_API_KEY!, scope: "organization" });
|
|
11
|
+
|
|
12
|
+
// Vercel AI SDK, OpenAI or Anthropic clients: detected by shape, no provider import here
|
|
13
|
+
const model = withCognia(openai("gpt-5.6"), { cognia, budget: { memories: 3, skills: 1 },
|
|
14
|
+
onInjected: (items) => ui.showSources(items) });
|
|
15
|
+
|
|
16
|
+
// direct
|
|
17
|
+
const ctx = await cognia.context({ task: userMessage }); // INJECT | ABSTAIN | UNAVAILABLE
|
|
18
|
+
await cognia.outcome(ctx.contextId!, { itemId, used: true, outcome: "success" });
|
|
19
|
+
|
|
20
|
+
// session learning: proposes, never saves
|
|
21
|
+
const session = new CogniaSession(cognia, conversationId);
|
|
22
|
+
const proposals = await session.end();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What is and is not a receipt
|
|
26
|
+
|
|
27
|
+
| receipt | meaning | evidence? |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| SEARCHED, RETRIEVED | Cognia looked, and returned items that cleared its gates | no |
|
|
30
|
+
| INJECTED | the SDK placed an item in front of the model | no |
|
|
31
|
+
| USED, success / failure | you said so with `outcome()`, or a human confirmed it | yes |
|
|
32
|
+
|
|
33
|
+
Injecting something never counts as using it. Only `outcome()` creates evidence, and only for items this context
|
|
34
|
+
returned. Dogfood, canary and operator traffic is classified server-side and never counts.
|
|
35
|
+
|
|
36
|
+
## Scope
|
|
37
|
+
|
|
38
|
+
`mine` = the identity's own memories and Skills. `organization` (default) = plus everything its organizations share
|
|
39
|
+
with it. `network` = plus the public network. Nothing private to another owner is ever returned; another owner is only
|
|
40
|
+
ever named by their public agent name.
|
|
41
|
+
|
|
42
|
+
## Abstention
|
|
43
|
+
|
|
44
|
+
When nothing clears the relevance gates the answer is `ABSTAIN` with a reason and no items. The SDK adds nothing to the
|
|
45
|
+
request in that case. Budgets default to 3 memories and 1 Skill; the gate is never loosened. The lookup times out after 1500 ms by default (`timeoutMs`), and a timeout is fail-open.
|
|
46
|
+
|
|
47
|
+
Docs: https://cognia.fun/developers · Source: github.com/cognia-dev/memory-backend (`packages/sdk`)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Cognia = exports.CogniaError = void 0;
|
|
4
|
+
class CogniaError extends Error {
|
|
5
|
+
status;
|
|
6
|
+
body;
|
|
7
|
+
constructor(message, status, body) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.body = body;
|
|
11
|
+
this.name = "CogniaError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.CogniaError = CogniaError;
|
|
15
|
+
class Cognia {
|
|
16
|
+
baseUrl;
|
|
17
|
+
scope;
|
|
18
|
+
budget;
|
|
19
|
+
timeoutMs;
|
|
20
|
+
key;
|
|
21
|
+
f;
|
|
22
|
+
onError;
|
|
23
|
+
constructor(o) {
|
|
24
|
+
if (!o.apiKey)
|
|
25
|
+
throw new Error("Cognia: apiKey is required");
|
|
26
|
+
this.key = o.apiKey;
|
|
27
|
+
this.baseUrl = (o.baseUrl ?? "https://api.cognia.fun").replace(/\/$/, "");
|
|
28
|
+
this.scope = o.scope ?? "organization";
|
|
29
|
+
this.budget = o.budget;
|
|
30
|
+
this.timeoutMs = o.timeoutMs ?? 1500;
|
|
31
|
+
this.f = o.fetch ?? fetch;
|
|
32
|
+
this.onError = o.onError;
|
|
33
|
+
}
|
|
34
|
+
async call(method, path, body, timeoutMs) {
|
|
35
|
+
const ac = new AbortController();
|
|
36
|
+
const t = timeoutMs ? setTimeout(() => ac.abort(), timeoutMs) : null;
|
|
37
|
+
try {
|
|
38
|
+
const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.0" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
|
|
39
|
+
let j = null;
|
|
40
|
+
try {
|
|
41
|
+
j = await r.json();
|
|
42
|
+
}
|
|
43
|
+
catch { /* no body */ }
|
|
44
|
+
if (!r.ok)
|
|
45
|
+
throw new CogniaError(`Cognia ${method} ${path} → ${r.status}`, r.status, j);
|
|
46
|
+
return j;
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (t)
|
|
50
|
+
clearTimeout(t);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The Memories and Skills this identity may use for `task`, or an explicit ABSTAIN. Never throws: on a timeout or a
|
|
55
|
+
* server error it resolves to `{ decision: "UNAVAILABLE" }` so the model call proceeds without context (fail-open).
|
|
56
|
+
* A bad credential is also reported this way, with the reason, so a misconfigured app degrades instead of breaking.
|
|
57
|
+
*/
|
|
58
|
+
async context(req) {
|
|
59
|
+
const body = typeof req === "string" ? { task: req } : req;
|
|
60
|
+
const payload = { task: body.task, scope: body.scope ?? this.scope, ...(body.budget ?? this.budget ? { budget: { ...this.budget, ...body.budget } } : {}), ...(body.session ? { session: body.session } : {}) };
|
|
61
|
+
try {
|
|
62
|
+
return await this.call("POST", "/v1/context", payload, this.timeoutMs);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
66
|
+
this.onError?.(err);
|
|
67
|
+
return { contextId: null, decision: "UNAVAILABLE", items: [], reason: err.name === "AbortError" ? "timeout" : err.message };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Tell Cognia which returned items were actually placed in front of the model. A usage receipt, never evidence. */
|
|
71
|
+
async injected(contextId, itemIds) {
|
|
72
|
+
if (!contextId || !itemIds.length)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
await this.call("POST", `/v1/context/${encodeURIComponent(contextId)}/injected`, { itemIds }, 2000);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
this.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Report what happened with one item: used or not, success / failure (/ neutral for a Skill). This is the only path to USED. */
|
|
82
|
+
outcome(contextId, input) {
|
|
83
|
+
return this.call("POST", `/v1/context/${encodeURIComponent(contextId)}/outcome`, input);
|
|
84
|
+
}
|
|
85
|
+
/** Session learning: propose memories from a session's turns. Nothing is saved; the host shows the proposals. */
|
|
86
|
+
extract(markdown, filename = "session.md") {
|
|
87
|
+
return this.call("POST", "/v1/memories/extract", { markdown, filename }, 60_000);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.Cognia = Cognia;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.provenanceLine = exports.renderContextBlock = exports.textOf = exports.prependSystem = exports.contextFor = exports.withCognia = exports.CogniaSession = exports.CogniaError = exports.Cognia = void 0;
|
|
4
|
+
var client_js_1 = require("./client.js");
|
|
5
|
+
Object.defineProperty(exports, "Cognia", { enumerable: true, get: function () { return client_js_1.Cognia; } });
|
|
6
|
+
Object.defineProperty(exports, "CogniaError", { enumerable: true, get: function () { return client_js_1.CogniaError; } });
|
|
7
|
+
var session_js_1 = require("./session.js");
|
|
8
|
+
Object.defineProperty(exports, "CogniaSession", { enumerable: true, get: function () { return session_js_1.CogniaSession; } });
|
|
9
|
+
var middleware_js_1 = require("./middleware.js");
|
|
10
|
+
Object.defineProperty(exports, "withCognia", { enumerable: true, get: function () { return middleware_js_1.withCognia; } });
|
|
11
|
+
Object.defineProperty(exports, "contextFor", { enumerable: true, get: function () { return middleware_js_1.contextFor; } });
|
|
12
|
+
Object.defineProperty(exports, "prependSystem", { enumerable: true, get: function () { return middleware_js_1.prependSystem; } });
|
|
13
|
+
Object.defineProperty(exports, "textOf", { enumerable: true, get: function () { return middleware_js_1.textOf; } });
|
|
14
|
+
var render_js_1 = require("./render.js");
|
|
15
|
+
Object.defineProperty(exports, "renderContextBlock", { enumerable: true, get: function () { return render_js_1.renderContextBlock; } });
|
|
16
|
+
Object.defineProperty(exports, "provenanceLine", { enumerable: true, get: function () { return render_js_1.provenanceLine; } });
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.textOf = textOf;
|
|
4
|
+
exports.contextFor = contextFor;
|
|
5
|
+
exports.prependSystem = prependSystem;
|
|
6
|
+
exports.withCognia = withCognia;
|
|
7
|
+
const render_js_1 = require("./render.js");
|
|
8
|
+
/** text of a message's content whatever the provider's shape (string, or parts with `text`) */
|
|
9
|
+
function textOf(content) {
|
|
10
|
+
if (typeof content === "string")
|
|
11
|
+
return content;
|
|
12
|
+
if (Array.isArray(content))
|
|
13
|
+
return content.map((p) => (p && typeof p === "object" && typeof p.text === "string" ? p.text : "")).filter(Boolean).join("\n");
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
const lastUser = (messages) => { for (let i = messages.length - 1; i >= 0; i--)
|
|
17
|
+
if (messages[i].role === "user")
|
|
18
|
+
return textOf(messages[i].content) || null; return null; };
|
|
19
|
+
/**
|
|
20
|
+
* One lookup for a request: the context block to prepend (or ""), plus the receipt side effects. Shared by every adapter.
|
|
21
|
+
* Fails open: an UNAVAILABLE or ABSTAIN result yields "" and the model call proceeds untouched.
|
|
22
|
+
*/
|
|
23
|
+
async function contextFor(o, messages) {
|
|
24
|
+
const task = (o.taskFrom ?? lastUser)(messages);
|
|
25
|
+
if (!task)
|
|
26
|
+
return { block: "", ctx: null };
|
|
27
|
+
const res = await o.cognia.context({ task, scope: o.scope, budget: o.budget, ...(o.session ? { session: o.session.next() } : {}) });
|
|
28
|
+
if (res.decision === "UNAVAILABLE") {
|
|
29
|
+
o.onEmpty?.(res.reason);
|
|
30
|
+
return { block: "", ctx: null };
|
|
31
|
+
}
|
|
32
|
+
if (res.decision !== "INJECT") {
|
|
33
|
+
o.onEmpty?.(res.abstain?.reason ?? "abstain");
|
|
34
|
+
return { block: "", ctx: null };
|
|
35
|
+
}
|
|
36
|
+
const block = (0, render_js_1.renderContextBlock)(res);
|
|
37
|
+
void o.cognia.injected(res.contextId, res.items.map((i) => i.id));
|
|
38
|
+
o.onInjected?.(res.items, res);
|
|
39
|
+
if (o.session)
|
|
40
|
+
o.session.add({ role: "user", content: task });
|
|
41
|
+
return { block, ctx: res };
|
|
42
|
+
}
|
|
43
|
+
const hasFn = (x, k) => !!x && typeof x[k] === "function";
|
|
44
|
+
const get = (x, path) => path.reduce((a, k) => (a && typeof a === "object" ? a[k] : undefined), x);
|
|
45
|
+
/** Prepend a system message (OpenAI / Anthropic message arrays): one system entry, Cognia first, then the host's own. */
|
|
46
|
+
function prependSystem(messages, block) {
|
|
47
|
+
if (!block)
|
|
48
|
+
return messages;
|
|
49
|
+
return [{ role: "system", content: block }, ...messages];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Wrap a model client so every call gets Cognia context first and the session learns from the replies.
|
|
53
|
+
* Supported shapes, detected structurally (no provider import):
|
|
54
|
+
* Vercel AI SDK LanguageModel (doGenerate/doStream) → a model whose `prompt` gets a leading system part
|
|
55
|
+
* OpenAI client (chat.completions.create) → a client whose create() prepends a system message
|
|
56
|
+
* Anthropic client (messages.create) → a client whose create() prefixes `system`
|
|
57
|
+
* Anything else is returned unchanged with a console warning, so a wrong import never breaks a call.
|
|
58
|
+
*/
|
|
59
|
+
function withCognia(target, o) {
|
|
60
|
+
if (hasFn(target, "doGenerate") || hasFn(target, "doStream"))
|
|
61
|
+
return wrapVercel(target, o);
|
|
62
|
+
if (hasFn(get(target, ["chat", "completions"]), "create"))
|
|
63
|
+
return wrapOpenAI(target, o);
|
|
64
|
+
if (hasFn(get(target, ["messages"]), "create"))
|
|
65
|
+
return wrapAnthropic(target, o);
|
|
66
|
+
console.warn("withCognia: unrecognised client shape; returning it unwrapped");
|
|
67
|
+
return target;
|
|
68
|
+
}
|
|
69
|
+
function wrapVercel(model, o) {
|
|
70
|
+
const transform = async (params) => {
|
|
71
|
+
const { block } = await contextFor(o, params.prompt);
|
|
72
|
+
if (!block)
|
|
73
|
+
return params;
|
|
74
|
+
return { ...params, prompt: [{ role: "system", content: block }, ...params.prompt] };
|
|
75
|
+
};
|
|
76
|
+
const learn = (r) => { if (o.session) {
|
|
77
|
+
const text = typeof r?.text === "string" ? r.text : "";
|
|
78
|
+
if (text)
|
|
79
|
+
o.session.add({ role: "assistant", content: text });
|
|
80
|
+
} return r; };
|
|
81
|
+
return new Proxy(model, {
|
|
82
|
+
get(t, k, recv) {
|
|
83
|
+
if (k === "doGenerate")
|
|
84
|
+
return async (p) => learn(await t.doGenerate(await transform(p)));
|
|
85
|
+
if (k === "doStream")
|
|
86
|
+
return async (p) => t.doStream(await transform(p));
|
|
87
|
+
return Reflect.get(t, k, recv);
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function wrapOpenAI(client, o) {
|
|
92
|
+
const create = client.chat.completions.create.bind(client.chat.completions);
|
|
93
|
+
const wrapped = async (p, ...rest) => {
|
|
94
|
+
const { block } = await contextFor(o, p.messages);
|
|
95
|
+
const r = await create(block ? { ...p, messages: prependSystem(p.messages, block) } : p, ...rest);
|
|
96
|
+
if (o.session) {
|
|
97
|
+
const text = textOf(get(r, ["choices", "0", "message", "content"]));
|
|
98
|
+
if (text)
|
|
99
|
+
o.session.add({ role: "assistant", content: text });
|
|
100
|
+
}
|
|
101
|
+
return r;
|
|
102
|
+
};
|
|
103
|
+
return new Proxy(client, { get(t, k, recv) { if (k === "chat")
|
|
104
|
+
return { ...t.chat, completions: { ...t.chat.completions, create: wrapped } }; return Reflect.get(t, k, recv); } });
|
|
105
|
+
}
|
|
106
|
+
function wrapAnthropic(client, o) {
|
|
107
|
+
const create = client.messages.create.bind(client.messages);
|
|
108
|
+
const wrapped = async (p, ...rest) => {
|
|
109
|
+
const { block } = await contextFor(o, p.messages);
|
|
110
|
+
let next = p;
|
|
111
|
+
if (block) {
|
|
112
|
+
const sys = typeof p.system === "string" ? `${block}\n\n${p.system}` : Array.isArray(p.system) ? [{ type: "text", text: block }, ...p.system] : block;
|
|
113
|
+
next = { ...p, system: sys };
|
|
114
|
+
}
|
|
115
|
+
const r = await create(next, ...rest);
|
|
116
|
+
if (o.session) {
|
|
117
|
+
const text = textOf(get(r, ["content"]));
|
|
118
|
+
if (text)
|
|
119
|
+
o.session.add({ role: "assistant", content: text });
|
|
120
|
+
}
|
|
121
|
+
return r;
|
|
122
|
+
};
|
|
123
|
+
return new Proxy(client, { get(t, k, recv) { if (k === "messages")
|
|
124
|
+
return { ...t.messages, create: wrapped }; return Reflect.get(t, k, recv); } });
|
|
125
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.provenanceLine = provenanceLine;
|
|
4
|
+
exports.renderContextBlock = renderContextBlock;
|
|
5
|
+
/** One provenance line the model can cite and a reader can look up: reference · owner · organization · evidence. */
|
|
6
|
+
function provenanceLine(it) {
|
|
7
|
+
const who = it.owner === "mine" ? "yours" : it.owner === "organization" ? `shared by ${it.provenance.ownerName ?? "a colleague"}` : `public, by ${it.provenance.ownerName ?? "another owner"}`;
|
|
8
|
+
const ev = it.provenance.verifiedUses > 0 ? `${it.provenance.verifiedUses} verified ${it.provenance.verifiedUses === 1 ? "use" : "uses"}` : "no verified use yet";
|
|
9
|
+
const mat = it.provenance.maturity ? ` · ${String(it.provenance.maturity).toLowerCase()}` : "";
|
|
10
|
+
return `[${it.displayId} · ${who}${mat} · ${ev}]`;
|
|
11
|
+
}
|
|
12
|
+
/** The context block placed before the model's instructions. Plain text, deterministic, nothing invented. */
|
|
13
|
+
function renderContextBlock(ctx) {
|
|
14
|
+
if (ctx.decision !== "INJECT" || !ctx.items.length)
|
|
15
|
+
return "";
|
|
16
|
+
const parts = [
|
|
17
|
+
"Cognia context: prior knowledge this identity may use. Each item names its source. Rely on it only when it fits the task; say which item you used by its reference; when none fits, say so.",
|
|
18
|
+
];
|
|
19
|
+
ctx.items.forEach((it, i) => {
|
|
20
|
+
const n = i + 1;
|
|
21
|
+
if (it.kind === "memory") {
|
|
22
|
+
parts.push(`\n[${n}] MEMORY ${provenanceLine(it)}\nTitle: ${it.title}\n${it.body.text}`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const steps = it.body.procedure.map((s, j) => ` ${j + 1}. ${s}`).join("\n");
|
|
26
|
+
const when = it.body.trigger.length ? `When: ${it.body.trigger.join("; ")}\n` : "";
|
|
27
|
+
const not = it.body.doNotApplyWhen.length ? `Do not apply when: ${it.body.doNotApplyWhen.join("; ")}\n` : "";
|
|
28
|
+
const verify = it.body.verification.length ? `Verify: ${it.body.verification.join("; ")}\n` : "";
|
|
29
|
+
parts.push(`\n[${n}] SKILL ${provenanceLine(it)}\nTitle: ${it.title}\n${it.body.summary}\n${when}${not}Procedure:\n${steps}\n${verify}`.trimEnd());
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return parts.join("\n");
|
|
33
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CogniaSession = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A conversation the middleware observes. `end()` proposes the few things worth remembering through Cognia's
|
|
6
|
+
* extraction endpoint (private-first, reviewed before anything is saved). It never saves by itself.
|
|
7
|
+
*/
|
|
8
|
+
class CogniaSession {
|
|
9
|
+
cognia;
|
|
10
|
+
id;
|
|
11
|
+
turns = [];
|
|
12
|
+
turn = 0;
|
|
13
|
+
constructor(cognia, id) {
|
|
14
|
+
this.cognia = cognia;
|
|
15
|
+
this.id = id;
|
|
16
|
+
}
|
|
17
|
+
next() { return { id: this.id, turn: this.turn++ }; }
|
|
18
|
+
add(t) { if (t.content && t.content.trim())
|
|
19
|
+
this.turns.push({ role: t.role, content: t.content.slice(0, 20_000) }); }
|
|
20
|
+
/** The session as Markdown, the shape the extraction endpoint reads. */
|
|
21
|
+
toMarkdown() {
|
|
22
|
+
return `# Session ${this.id}\n\n` + this.turns.filter((t) => t.role === "user" || t.role === "assistant").map((t) => `## ${t.role === "user" ? "User" : "Assistant"}\n\n${t.content}\n`).join("\n");
|
|
23
|
+
}
|
|
24
|
+
/** Propose memories from this session. Returns the server's proposals; saving is the host's decision. */
|
|
25
|
+
async end() {
|
|
26
|
+
if (!this.turns.some((t) => t.role === "assistant"))
|
|
27
|
+
return null;
|
|
28
|
+
const md = this.toMarkdown();
|
|
29
|
+
if (md.length > 180_000)
|
|
30
|
+
return this.cognia.extract(md.slice(-180_000), `session-${this.id}.md`);
|
|
31
|
+
return this.cognia.extract(md, `session-${this.id}.md`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.CogniaSession = CogniaSession;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ContextRequest, ContextResult, OutcomeInput, OutcomeResponse, ExtractResponse, Scope } from "./types.js";
|
|
2
|
+
export interface CogniaOptions {
|
|
3
|
+
/** the agent's credential (cognia_sk_…); never logged, never put in a URL */
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** default scope for context(); the identity's grants decide what that means */
|
|
7
|
+
scope?: Scope;
|
|
8
|
+
/** default budget */
|
|
9
|
+
budget?: {
|
|
10
|
+
memories?: number;
|
|
11
|
+
skills?: number;
|
|
12
|
+
};
|
|
13
|
+
/** how long a context lookup may take before the model call goes on without it (fail-open). Default 1500 ms: a
|
|
14
|
+
network-scope lookup that routes Skills measured about 1.3 s server-side on 2026-10-06; mine-scope about 50 ms. */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
/** receives every non-fatal problem (timeouts, 5xx); nothing is thrown out of context() */
|
|
18
|
+
onError?: (e: Error) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare class CogniaError extends Error {
|
|
21
|
+
readonly status: number;
|
|
22
|
+
readonly body: unknown;
|
|
23
|
+
constructor(message: string, status: number, body: unknown);
|
|
24
|
+
}
|
|
25
|
+
export declare class Cognia {
|
|
26
|
+
readonly baseUrl: string;
|
|
27
|
+
readonly scope: Scope;
|
|
28
|
+
readonly budget: {
|
|
29
|
+
memories?: number;
|
|
30
|
+
skills?: number;
|
|
31
|
+
} | undefined;
|
|
32
|
+
readonly timeoutMs: number;
|
|
33
|
+
private readonly key;
|
|
34
|
+
private readonly f;
|
|
35
|
+
private readonly onError;
|
|
36
|
+
constructor(o: CogniaOptions);
|
|
37
|
+
private call;
|
|
38
|
+
/**
|
|
39
|
+
* The Memories and Skills this identity may use for `task`, or an explicit ABSTAIN. Never throws: on a timeout or a
|
|
40
|
+
* server error it resolves to `{ decision: "UNAVAILABLE" }` so the model call proceeds without context (fail-open).
|
|
41
|
+
* A bad credential is also reported this way, with the reason, so a misconfigured app degrades instead of breaking.
|
|
42
|
+
*/
|
|
43
|
+
context(req: ContextRequest | string): Promise<ContextResult>;
|
|
44
|
+
/** Tell Cognia which returned items were actually placed in front of the model. A usage receipt, never evidence. */
|
|
45
|
+
injected(contextId: string, itemIds: string[]): Promise<void>;
|
|
46
|
+
/** Report what happened with one item: used or not, success / failure (/ neutral for a Skill). This is the only path to USED. */
|
|
47
|
+
outcome(contextId: string, input: OutcomeInput): Promise<OutcomeResponse>;
|
|
48
|
+
/** Session learning: propose memories from a session's turns. Nothing is saved; the host shows the proposals. */
|
|
49
|
+
extract(markdown: string, filename?: string): Promise<ExtractResponse>;
|
|
50
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export class CogniaError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
constructor(message, status, body) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.body = body;
|
|
8
|
+
this.name = "CogniaError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class Cognia {
|
|
12
|
+
baseUrl;
|
|
13
|
+
scope;
|
|
14
|
+
budget;
|
|
15
|
+
timeoutMs;
|
|
16
|
+
key;
|
|
17
|
+
f;
|
|
18
|
+
onError;
|
|
19
|
+
constructor(o) {
|
|
20
|
+
if (!o.apiKey)
|
|
21
|
+
throw new Error("Cognia: apiKey is required");
|
|
22
|
+
this.key = o.apiKey;
|
|
23
|
+
this.baseUrl = (o.baseUrl ?? "https://api.cognia.fun").replace(/\/$/, "");
|
|
24
|
+
this.scope = o.scope ?? "organization";
|
|
25
|
+
this.budget = o.budget;
|
|
26
|
+
this.timeoutMs = o.timeoutMs ?? 1500;
|
|
27
|
+
this.f = o.fetch ?? fetch;
|
|
28
|
+
this.onError = o.onError;
|
|
29
|
+
}
|
|
30
|
+
async call(method, path, body, timeoutMs) {
|
|
31
|
+
const ac = new AbortController();
|
|
32
|
+
const t = timeoutMs ? setTimeout(() => ac.abort(), timeoutMs) : null;
|
|
33
|
+
try {
|
|
34
|
+
const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.0" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
|
|
35
|
+
let j = null;
|
|
36
|
+
try {
|
|
37
|
+
j = await r.json();
|
|
38
|
+
}
|
|
39
|
+
catch { /* no body */ }
|
|
40
|
+
if (!r.ok)
|
|
41
|
+
throw new CogniaError(`Cognia ${method} ${path} → ${r.status}`, r.status, j);
|
|
42
|
+
return j;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
if (t)
|
|
46
|
+
clearTimeout(t);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The Memories and Skills this identity may use for `task`, or an explicit ABSTAIN. Never throws: on a timeout or a
|
|
51
|
+
* server error it resolves to `{ decision: "UNAVAILABLE" }` so the model call proceeds without context (fail-open).
|
|
52
|
+
* A bad credential is also reported this way, with the reason, so a misconfigured app degrades instead of breaking.
|
|
53
|
+
*/
|
|
54
|
+
async context(req) {
|
|
55
|
+
const body = typeof req === "string" ? { task: req } : req;
|
|
56
|
+
const payload = { task: body.task, scope: body.scope ?? this.scope, ...(body.budget ?? this.budget ? { budget: { ...this.budget, ...body.budget } } : {}), ...(body.session ? { session: body.session } : {}) };
|
|
57
|
+
try {
|
|
58
|
+
return await this.call("POST", "/v1/context", payload, this.timeoutMs);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
62
|
+
this.onError?.(err);
|
|
63
|
+
return { contextId: null, decision: "UNAVAILABLE", items: [], reason: err.name === "AbortError" ? "timeout" : err.message };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Tell Cognia which returned items were actually placed in front of the model. A usage receipt, never evidence. */
|
|
67
|
+
async injected(contextId, itemIds) {
|
|
68
|
+
if (!contextId || !itemIds.length)
|
|
69
|
+
return;
|
|
70
|
+
try {
|
|
71
|
+
await this.call("POST", `/v1/context/${encodeURIComponent(contextId)}/injected`, { itemIds }, 2000);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
this.onError?.(e instanceof Error ? e : new Error(String(e)));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Report what happened with one item: used or not, success / failure (/ neutral for a Skill). This is the only path to USED. */
|
|
78
|
+
outcome(contextId, input) {
|
|
79
|
+
return this.call("POST", `/v1/context/${encodeURIComponent(contextId)}/outcome`, input);
|
|
80
|
+
}
|
|
81
|
+
/** Session learning: propose memories from a session's turns. Nothing is saved; the host shows the proposals. */
|
|
82
|
+
extract(markdown, filename = "session.md") {
|
|
83
|
+
return this.call("POST", "/v1/memories/extract", { markdown, filename }, 60_000);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Cognia, CogniaError, type CogniaOptions } from "./client.js";
|
|
2
|
+
export { CogniaSession, type SessionTurn } from "./session.js";
|
|
3
|
+
export { withCognia, contextFor, prependSystem, textOf, type WithCogniaOptions } from "./middleware.js";
|
|
4
|
+
export { renderContextBlock, provenanceLine } from "./render.js";
|
|
5
|
+
export type * from "./types.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Cognia } from "./client.js";
|
|
2
|
+
import type { CogniaSession } from "./session.js";
|
|
3
|
+
import type { ContextItem, ContextResponse, Scope } from "./types.js";
|
|
4
|
+
export interface WithCogniaOptions {
|
|
5
|
+
cognia: Cognia;
|
|
6
|
+
scope?: Scope;
|
|
7
|
+
budget?: {
|
|
8
|
+
memories?: number;
|
|
9
|
+
skills?: number;
|
|
10
|
+
};
|
|
11
|
+
session?: CogniaSession;
|
|
12
|
+
/** called with the items that were placed in front of the model (and the contextId), e.g. to show "Sources" */
|
|
13
|
+
onInjected?: (items: ContextItem[], ctx: ContextResponse) => void;
|
|
14
|
+
/** called when Cognia abstained or was unavailable */
|
|
15
|
+
onEmpty?: (reason: string) => void;
|
|
16
|
+
/** which message to read the task from; default: the last user message */
|
|
17
|
+
taskFrom?: (messages: Array<{
|
|
18
|
+
role: string;
|
|
19
|
+
content: unknown;
|
|
20
|
+
}>) => string | null;
|
|
21
|
+
}
|
|
22
|
+
/** text of a message's content whatever the provider's shape (string, or parts with `text`) */
|
|
23
|
+
export declare function textOf(content: unknown): string;
|
|
24
|
+
/**
|
|
25
|
+
* One lookup for a request: the context block to prepend (or ""), plus the receipt side effects. Shared by every adapter.
|
|
26
|
+
* Fails open: an UNAVAILABLE or ABSTAIN result yields "" and the model call proceeds untouched.
|
|
27
|
+
*/
|
|
28
|
+
export declare function contextFor(o: WithCogniaOptions, messages: Array<{
|
|
29
|
+
role: string;
|
|
30
|
+
content: unknown;
|
|
31
|
+
}>): Promise<{
|
|
32
|
+
block: string;
|
|
33
|
+
ctx: ContextResponse | null;
|
|
34
|
+
}>;
|
|
35
|
+
/** Prepend a system message (OpenAI / Anthropic message arrays): one system entry, Cognia first, then the host's own. */
|
|
36
|
+
export declare function prependSystem<M extends {
|
|
37
|
+
role: string;
|
|
38
|
+
content: unknown;
|
|
39
|
+
}>(messages: M[], block: string): M[];
|
|
40
|
+
/**
|
|
41
|
+
* Wrap a model client so every call gets Cognia context first and the session learns from the replies.
|
|
42
|
+
* Supported shapes, detected structurally (no provider import):
|
|
43
|
+
* Vercel AI SDK LanguageModel (doGenerate/doStream) → a model whose `prompt` gets a leading system part
|
|
44
|
+
* OpenAI client (chat.completions.create) → a client whose create() prepends a system message
|
|
45
|
+
* Anthropic client (messages.create) → a client whose create() prefixes `system`
|
|
46
|
+
* Anything else is returned unchanged with a console warning, so a wrong import never breaks a call.
|
|
47
|
+
*/
|
|
48
|
+
export declare function withCognia<T>(target: T, o: WithCogniaOptions): T;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { renderContextBlock } from "./render.js";
|
|
2
|
+
/** text of a message's content whatever the provider's shape (string, or parts with `text`) */
|
|
3
|
+
export function textOf(content) {
|
|
4
|
+
if (typeof content === "string")
|
|
5
|
+
return content;
|
|
6
|
+
if (Array.isArray(content))
|
|
7
|
+
return content.map((p) => (p && typeof p === "object" && typeof p.text === "string" ? p.text : "")).filter(Boolean).join("\n");
|
|
8
|
+
return "";
|
|
9
|
+
}
|
|
10
|
+
const lastUser = (messages) => { for (let i = messages.length - 1; i >= 0; i--)
|
|
11
|
+
if (messages[i].role === "user")
|
|
12
|
+
return textOf(messages[i].content) || null; return null; };
|
|
13
|
+
/**
|
|
14
|
+
* One lookup for a request: the context block to prepend (or ""), plus the receipt side effects. Shared by every adapter.
|
|
15
|
+
* Fails open: an UNAVAILABLE or ABSTAIN result yields "" and the model call proceeds untouched.
|
|
16
|
+
*/
|
|
17
|
+
export async function contextFor(o, messages) {
|
|
18
|
+
const task = (o.taskFrom ?? lastUser)(messages);
|
|
19
|
+
if (!task)
|
|
20
|
+
return { block: "", ctx: null };
|
|
21
|
+
const res = await o.cognia.context({ task, scope: o.scope, budget: o.budget, ...(o.session ? { session: o.session.next() } : {}) });
|
|
22
|
+
if (res.decision === "UNAVAILABLE") {
|
|
23
|
+
o.onEmpty?.(res.reason);
|
|
24
|
+
return { block: "", ctx: null };
|
|
25
|
+
}
|
|
26
|
+
if (res.decision !== "INJECT") {
|
|
27
|
+
o.onEmpty?.(res.abstain?.reason ?? "abstain");
|
|
28
|
+
return { block: "", ctx: null };
|
|
29
|
+
}
|
|
30
|
+
const block = renderContextBlock(res);
|
|
31
|
+
void o.cognia.injected(res.contextId, res.items.map((i) => i.id));
|
|
32
|
+
o.onInjected?.(res.items, res);
|
|
33
|
+
if (o.session)
|
|
34
|
+
o.session.add({ role: "user", content: task });
|
|
35
|
+
return { block, ctx: res };
|
|
36
|
+
}
|
|
37
|
+
const hasFn = (x, k) => !!x && typeof x[k] === "function";
|
|
38
|
+
const get = (x, path) => path.reduce((a, k) => (a && typeof a === "object" ? a[k] : undefined), x);
|
|
39
|
+
/** Prepend a system message (OpenAI / Anthropic message arrays): one system entry, Cognia first, then the host's own. */
|
|
40
|
+
export function prependSystem(messages, block) {
|
|
41
|
+
if (!block)
|
|
42
|
+
return messages;
|
|
43
|
+
return [{ role: "system", content: block }, ...messages];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Wrap a model client so every call gets Cognia context first and the session learns from the replies.
|
|
47
|
+
* Supported shapes, detected structurally (no provider import):
|
|
48
|
+
* Vercel AI SDK LanguageModel (doGenerate/doStream) → a model whose `prompt` gets a leading system part
|
|
49
|
+
* OpenAI client (chat.completions.create) → a client whose create() prepends a system message
|
|
50
|
+
* Anthropic client (messages.create) → a client whose create() prefixes `system`
|
|
51
|
+
* Anything else is returned unchanged with a console warning, so a wrong import never breaks a call.
|
|
52
|
+
*/
|
|
53
|
+
export function withCognia(target, o) {
|
|
54
|
+
if (hasFn(target, "doGenerate") || hasFn(target, "doStream"))
|
|
55
|
+
return wrapVercel(target, o);
|
|
56
|
+
if (hasFn(get(target, ["chat", "completions"]), "create"))
|
|
57
|
+
return wrapOpenAI(target, o);
|
|
58
|
+
if (hasFn(get(target, ["messages"]), "create"))
|
|
59
|
+
return wrapAnthropic(target, o);
|
|
60
|
+
console.warn("withCognia: unrecognised client shape; returning it unwrapped");
|
|
61
|
+
return target;
|
|
62
|
+
}
|
|
63
|
+
function wrapVercel(model, o) {
|
|
64
|
+
const transform = async (params) => {
|
|
65
|
+
const { block } = await contextFor(o, params.prompt);
|
|
66
|
+
if (!block)
|
|
67
|
+
return params;
|
|
68
|
+
return { ...params, prompt: [{ role: "system", content: block }, ...params.prompt] };
|
|
69
|
+
};
|
|
70
|
+
const learn = (r) => { if (o.session) {
|
|
71
|
+
const text = typeof r?.text === "string" ? r.text : "";
|
|
72
|
+
if (text)
|
|
73
|
+
o.session.add({ role: "assistant", content: text });
|
|
74
|
+
} return r; };
|
|
75
|
+
return new Proxy(model, {
|
|
76
|
+
get(t, k, recv) {
|
|
77
|
+
if (k === "doGenerate")
|
|
78
|
+
return async (p) => learn(await t.doGenerate(await transform(p)));
|
|
79
|
+
if (k === "doStream")
|
|
80
|
+
return async (p) => t.doStream(await transform(p));
|
|
81
|
+
return Reflect.get(t, k, recv);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function wrapOpenAI(client, o) {
|
|
86
|
+
const create = client.chat.completions.create.bind(client.chat.completions);
|
|
87
|
+
const wrapped = async (p, ...rest) => {
|
|
88
|
+
const { block } = await contextFor(o, p.messages);
|
|
89
|
+
const r = await create(block ? { ...p, messages: prependSystem(p.messages, block) } : p, ...rest);
|
|
90
|
+
if (o.session) {
|
|
91
|
+
const text = textOf(get(r, ["choices", "0", "message", "content"]));
|
|
92
|
+
if (text)
|
|
93
|
+
o.session.add({ role: "assistant", content: text });
|
|
94
|
+
}
|
|
95
|
+
return r;
|
|
96
|
+
};
|
|
97
|
+
return new Proxy(client, { get(t, k, recv) { if (k === "chat")
|
|
98
|
+
return { ...t.chat, completions: { ...t.chat.completions, create: wrapped } }; return Reflect.get(t, k, recv); } });
|
|
99
|
+
}
|
|
100
|
+
function wrapAnthropic(client, o) {
|
|
101
|
+
const create = client.messages.create.bind(client.messages);
|
|
102
|
+
const wrapped = async (p, ...rest) => {
|
|
103
|
+
const { block } = await contextFor(o, p.messages);
|
|
104
|
+
let next = p;
|
|
105
|
+
if (block) {
|
|
106
|
+
const sys = typeof p.system === "string" ? `${block}\n\n${p.system}` : Array.isArray(p.system) ? [{ type: "text", text: block }, ...p.system] : block;
|
|
107
|
+
next = { ...p, system: sys };
|
|
108
|
+
}
|
|
109
|
+
const r = await create(next, ...rest);
|
|
110
|
+
if (o.session) {
|
|
111
|
+
const text = textOf(get(r, ["content"]));
|
|
112
|
+
if (text)
|
|
113
|
+
o.session.add({ role: "assistant", content: text });
|
|
114
|
+
}
|
|
115
|
+
return r;
|
|
116
|
+
};
|
|
117
|
+
return new Proxy(client, { get(t, k, recv) { if (k === "messages")
|
|
118
|
+
return { ...t.messages, create: wrapped }; return Reflect.get(t, k, recv); } });
|
|
119
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ContextItem, ContextResponse } from "./types.js";
|
|
2
|
+
/** One provenance line the model can cite and a reader can look up: reference · owner · organization · evidence. */
|
|
3
|
+
export declare function provenanceLine(it: ContextItem): string;
|
|
4
|
+
/** The context block placed before the model's instructions. Plain text, deterministic, nothing invented. */
|
|
5
|
+
export declare function renderContextBlock(ctx: ContextResponse): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** One provenance line the model can cite and a reader can look up: reference · owner · organization · evidence. */
|
|
2
|
+
export function provenanceLine(it) {
|
|
3
|
+
const who = it.owner === "mine" ? "yours" : it.owner === "organization" ? `shared by ${it.provenance.ownerName ?? "a colleague"}` : `public, by ${it.provenance.ownerName ?? "another owner"}`;
|
|
4
|
+
const ev = it.provenance.verifiedUses > 0 ? `${it.provenance.verifiedUses} verified ${it.provenance.verifiedUses === 1 ? "use" : "uses"}` : "no verified use yet";
|
|
5
|
+
const mat = it.provenance.maturity ? ` · ${String(it.provenance.maturity).toLowerCase()}` : "";
|
|
6
|
+
return `[${it.displayId} · ${who}${mat} · ${ev}]`;
|
|
7
|
+
}
|
|
8
|
+
/** The context block placed before the model's instructions. Plain text, deterministic, nothing invented. */
|
|
9
|
+
export function renderContextBlock(ctx) {
|
|
10
|
+
if (ctx.decision !== "INJECT" || !ctx.items.length)
|
|
11
|
+
return "";
|
|
12
|
+
const parts = [
|
|
13
|
+
"Cognia context: prior knowledge this identity may use. Each item names its source. Rely on it only when it fits the task; say which item you used by its reference; when none fits, say so.",
|
|
14
|
+
];
|
|
15
|
+
ctx.items.forEach((it, i) => {
|
|
16
|
+
const n = i + 1;
|
|
17
|
+
if (it.kind === "memory") {
|
|
18
|
+
parts.push(`\n[${n}] MEMORY ${provenanceLine(it)}\nTitle: ${it.title}\n${it.body.text}`);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const steps = it.body.procedure.map((s, j) => ` ${j + 1}. ${s}`).join("\n");
|
|
22
|
+
const when = it.body.trigger.length ? `When: ${it.body.trigger.join("; ")}\n` : "";
|
|
23
|
+
const not = it.body.doNotApplyWhen.length ? `Do not apply when: ${it.body.doNotApplyWhen.join("; ")}\n` : "";
|
|
24
|
+
const verify = it.body.verification.length ? `Verify: ${it.body.verification.join("; ")}\n` : "";
|
|
25
|
+
parts.push(`\n[${n}] SKILL ${provenanceLine(it)}\nTitle: ${it.title}\n${it.body.summary}\n${when}${not}Procedure:\n${steps}\n${verify}`.trimEnd());
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return parts.join("\n");
|
|
29
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Cognia } from "./client.js";
|
|
2
|
+
import type { ExtractResponse } from "./types.js";
|
|
3
|
+
export interface SessionTurn {
|
|
4
|
+
role: "user" | "assistant" | "system" | "tool";
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A conversation the middleware observes. `end()` proposes the few things worth remembering through Cognia's
|
|
9
|
+
* extraction endpoint (private-first, reviewed before anything is saved). It never saves by itself.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CogniaSession {
|
|
12
|
+
private readonly cognia;
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly turns: SessionTurn[];
|
|
15
|
+
private turn;
|
|
16
|
+
constructor(cognia: Cognia, id: string);
|
|
17
|
+
next(): {
|
|
18
|
+
id: string;
|
|
19
|
+
turn: number;
|
|
20
|
+
};
|
|
21
|
+
add(t: SessionTurn): void;
|
|
22
|
+
/** The session as Markdown, the shape the extraction endpoint reads. */
|
|
23
|
+
toMarkdown(): string;
|
|
24
|
+
/** Propose memories from this session. Returns the server's proposals; saving is the host's decision. */
|
|
25
|
+
end(): Promise<ExtractResponse | null>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A conversation the middleware observes. `end()` proposes the few things worth remembering through Cognia's
|
|
3
|
+
* extraction endpoint (private-first, reviewed before anything is saved). It never saves by itself.
|
|
4
|
+
*/
|
|
5
|
+
export class CogniaSession {
|
|
6
|
+
cognia;
|
|
7
|
+
id;
|
|
8
|
+
turns = [];
|
|
9
|
+
turn = 0;
|
|
10
|
+
constructor(cognia, id) {
|
|
11
|
+
this.cognia = cognia;
|
|
12
|
+
this.id = id;
|
|
13
|
+
}
|
|
14
|
+
next() { return { id: this.id, turn: this.turn++ }; }
|
|
15
|
+
add(t) { if (t.content && t.content.trim())
|
|
16
|
+
this.turns.push({ role: t.role, content: t.content.slice(0, 20_000) }); }
|
|
17
|
+
/** The session as Markdown, the shape the extraction endpoint reads. */
|
|
18
|
+
toMarkdown() {
|
|
19
|
+
return `# Session ${this.id}\n\n` + this.turns.filter((t) => t.role === "user" || t.role === "assistant").map((t) => `## ${t.role === "user" ? "User" : "Assistant"}\n\n${t.content}\n`).join("\n");
|
|
20
|
+
}
|
|
21
|
+
/** Propose memories from this session. Returns the server's proposals; saving is the host's decision. */
|
|
22
|
+
async end() {
|
|
23
|
+
if (!this.turns.some((t) => t.role === "assistant"))
|
|
24
|
+
return null;
|
|
25
|
+
const md = this.toMarkdown();
|
|
26
|
+
if (md.length > 180_000)
|
|
27
|
+
return this.cognia.extract(md.slice(-180_000), `session-${this.id}.md`);
|
|
28
|
+
return this.cognia.extract(md, `session-${this.id}.md`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/** The wire shapes of POST /v1/context and its two follow-ups (docs/V2_AUTOMATIC_COGNIA.md). */
|
|
2
|
+
export type Scope = "mine" | "organization" | "network";
|
|
3
|
+
export type Owner = "mine" | "organization" | "network";
|
|
4
|
+
export interface ContextRequest {
|
|
5
|
+
task: string;
|
|
6
|
+
scope?: Scope;
|
|
7
|
+
budget?: {
|
|
8
|
+
memories?: number;
|
|
9
|
+
skills?: number;
|
|
10
|
+
};
|
|
11
|
+
session?: {
|
|
12
|
+
id: string;
|
|
13
|
+
turn: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface MemoryItem {
|
|
17
|
+
kind: "memory";
|
|
18
|
+
id: string;
|
|
19
|
+
displayId: string;
|
|
20
|
+
title: string;
|
|
21
|
+
owner: Owner;
|
|
22
|
+
body: {
|
|
23
|
+
text: string;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
};
|
|
26
|
+
provenance: {
|
|
27
|
+
ownerName: string | null;
|
|
28
|
+
organizationName: string | null;
|
|
29
|
+
maturity: string | null;
|
|
30
|
+
verifiedUses: number;
|
|
31
|
+
created: string | null;
|
|
32
|
+
};
|
|
33
|
+
score: number | null;
|
|
34
|
+
}
|
|
35
|
+
export interface SkillItem {
|
|
36
|
+
kind: "skill";
|
|
37
|
+
id: string;
|
|
38
|
+
displayId: string;
|
|
39
|
+
title: string;
|
|
40
|
+
owner: Owner;
|
|
41
|
+
body: {
|
|
42
|
+
summary: string;
|
|
43
|
+
trigger: string[];
|
|
44
|
+
procedure: string[];
|
|
45
|
+
verification: string[];
|
|
46
|
+
doNotApplyWhen: string[];
|
|
47
|
+
};
|
|
48
|
+
provenance: {
|
|
49
|
+
ownerName: string | null;
|
|
50
|
+
maturity: string;
|
|
51
|
+
verifiedUses: number;
|
|
52
|
+
routeId: string | null;
|
|
53
|
+
};
|
|
54
|
+
score: number | null;
|
|
55
|
+
}
|
|
56
|
+
export type ContextItem = MemoryItem | SkillItem;
|
|
57
|
+
export interface ContextResponse {
|
|
58
|
+
contextId: string;
|
|
59
|
+
decision: "INJECT" | "ABSTAIN";
|
|
60
|
+
abstain?: {
|
|
61
|
+
reason: string;
|
|
62
|
+
};
|
|
63
|
+
scope: Scope;
|
|
64
|
+
items: ContextItem[];
|
|
65
|
+
receipts: Array<{
|
|
66
|
+
kind: "SEARCHED" | "RETRIEVED";
|
|
67
|
+
itemId?: string;
|
|
68
|
+
at: string;
|
|
69
|
+
}>;
|
|
70
|
+
metrics: {
|
|
71
|
+
ms: {
|
|
72
|
+
search: number;
|
|
73
|
+
total: number;
|
|
74
|
+
};
|
|
75
|
+
considered: number;
|
|
76
|
+
returned: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** What `context()` returns when Cognia could not answer in time: the model call goes on without context. */
|
|
80
|
+
export interface ContextUnavailable {
|
|
81
|
+
contextId: null;
|
|
82
|
+
decision: "UNAVAILABLE";
|
|
83
|
+
items: [];
|
|
84
|
+
reason: string;
|
|
85
|
+
}
|
|
86
|
+
export type ContextResult = ContextResponse | ContextUnavailable;
|
|
87
|
+
export interface OutcomeInput {
|
|
88
|
+
itemId: string;
|
|
89
|
+
used: boolean;
|
|
90
|
+
outcome: "success" | "failure" | "neutral";
|
|
91
|
+
verification?: {
|
|
92
|
+
type: "agent_reported" | "signal_verified";
|
|
93
|
+
verifierType?: string;
|
|
94
|
+
evidenceHash?: string;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export interface OutcomeResponse {
|
|
98
|
+
contextId: string;
|
|
99
|
+
itemId: string;
|
|
100
|
+
recorded: boolean;
|
|
101
|
+
recallId?: string;
|
|
102
|
+
routeId?: string;
|
|
103
|
+
used?: boolean;
|
|
104
|
+
outcome?: string;
|
|
105
|
+
verification?: string;
|
|
106
|
+
detail?: string;
|
|
107
|
+
}
|
|
108
|
+
export interface ExtractProposal {
|
|
109
|
+
title?: string;
|
|
110
|
+
summary?: string;
|
|
111
|
+
[k: string]: unknown;
|
|
112
|
+
}
|
|
113
|
+
export interface ExtractResponse {
|
|
114
|
+
candidates?: ExtractProposal[];
|
|
115
|
+
proposals?: ExtractProposal[];
|
|
116
|
+
[k: string]: unknown;
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognia-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cognia for your model calls: one line gives any agent the Memories and Skills it may use, with provenance and honest receipts. Fails open.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Cognia",
|
|
7
|
+
"homepage": "https://cognia.fun/developers",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/cognia-dev/memory-backend.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cognia",
|
|
14
|
+
"memory",
|
|
15
|
+
"agents",
|
|
16
|
+
"skills",
|
|
17
|
+
"vercel-ai-sdk",
|
|
18
|
+
"openai",
|
|
19
|
+
"anthropic",
|
|
20
|
+
"mcp"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/cjs/index.js",
|
|
24
|
+
"module": "./dist/esm/index.js",
|
|
25
|
+
"types": "./dist/esm/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/esm/index.d.ts",
|
|
29
|
+
"import": "./dist/esm/index.js",
|
|
30
|
+
"require": "./dist/cjs/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20"
|
|
39
|
+
},
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"typescript": "^5.6.0",
|
|
43
|
+
"vitest": "^3.2.0",
|
|
44
|
+
"@types/node": "^22.0.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
|
|
48
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
49
|
+
"test": "vitest run"
|
|
50
|
+
}
|
|
6
51
|
}
|