cognia-sdk 0.1.1 → 0.1.3

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 CHANGED
@@ -33,6 +33,22 @@ const proposals = await session.end();
33
33
  Injecting something never counts as using it. Only `outcome()` creates evidence, and only for items this context
34
34
  returned. Dogfood, canary and operator traffic is classified server-side and never counts.
35
35
 
36
+
37
+ ## Auth: key or delegated token
38
+
39
+ ```ts
40
+ // a developer credential or an SDK credential (cognia_sk_…)
41
+ new Cognia({ apiKey: process.env.COGNIA_API_KEY })
42
+
43
+ // delegated auth: the host already holds a Cognia bearer (e.g. the MCP OAuth access token)
44
+ new Cognia({ tokenProvider: async () => await getCogniaToken() }) // called on every request, never cached, never in errors
45
+ ```
46
+
47
+ If your identity lives in a connected client (Claude, ChatGPT, OpenClaw…) and the host hides its token, ask the connected
48
+ Cognia to run `sdk_link_start`: it shows a one-time code and a Cloud link; you approve on the website and receive an
49
+ **SDK credential** — a separate, narrow credential that can read context and report outcomes only. Details:
50
+ `docs/SDK_CREDENTIALS.md` in the Cognia repository.
51
+
36
52
  ## Scope
37
53
 
38
54
  `mine` = the identity's own memories and Skills. `organization` (default) = plus everything its organizations share
@@ -9,7 +9,32 @@ function shapeContext(r) {
9
9
  const o = r;
10
10
  if (typeof o.contextId !== "string" || (o.decision !== "INJECT" && o.decision !== "ABSTAIN"))
11
11
  return null;
12
- const items = Array.isArray(o.items) ? o.items.filter((it) => it && typeof it === "object" && typeof it.id === "string" && (it.kind === "memory" || it.kind === "skill") && typeof it.title === "string" && !!it.body) : [];
12
+ // every item is NORMALISED, not merely filtered (independent E2E F01 retest, 2026-10-07): a missing or null
13
+ // provenance, a Skill body whose arrays arrive as strings, a non-string title — none of these may reach the
14
+ // renderer and throw, because a throw here means the host model is never called (the fail-open contract)
15
+ const arr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : typeof v === "string" && v ? [v] : [];
16
+ const str = (v, d = "") => (typeof v === "string" ? v : d);
17
+ const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
18
+ const q = (p) => (p && typeof p === "object" ? p : {});
19
+ const sOrNull = (v) => (typeof v === "string" ? v : null);
20
+ const items = (Array.isArray(o.items) ? o.items : []).flatMap((raw) => {
21
+ if (!raw || typeof raw !== "object")
22
+ return [];
23
+ const it = raw;
24
+ if (typeof it.id !== "string" || (it.kind !== "memory" && it.kind !== "skill"))
25
+ return [];
26
+ const owner = it.owner === "mine" ? "mine" : it.owner === "organization" ? "organization" : "network";
27
+ const body = q(it.body), p = q(it.provenance);
28
+ const base = { id: it.id, displayId: str(it.displayId, it.id), title: str(it.title, "(untitled)"), owner, score: typeof it.score === "number" && Number.isFinite(it.score) ? it.score : null };
29
+ if (it.kind === "memory") {
30
+ const m = { ...base, kind: "memory", body: { text: str(body.text), kind: sOrNull(body.kind) },
31
+ provenance: { ownerName: sOrNull(p.ownerName), organizationName: sOrNull(p.organizationName), maturity: sOrNull(p.maturity), verifiedUses: num(p.verifiedUses), created: sOrNull(p.created) } };
32
+ return [m];
33
+ }
34
+ const k = { ...base, kind: "skill", body: { summary: str(body.summary), trigger: arr(body.trigger), procedure: arr(body.procedure), verification: arr(body.verification), doNotApplyWhen: arr(body.doNotApplyWhen) },
35
+ provenance: { ownerName: sOrNull(p.ownerName), maturity: str(p.maturity, "unknown"), verifiedUses: num(p.verifiedUses), routeId: sOrNull(p.routeId) } };
36
+ return [k];
37
+ });
13
38
  const decision = o.decision === "INJECT" && items.length ? "INJECT" : "ABSTAIN";
14
39
  const scope = o.scope === "mine" || o.scope === "organization" || o.scope === "network" ? o.scope : "organization";
15
40
  const metrics = (o.metrics && typeof o.metrics === "object" ? o.metrics : { ms: { search: 0, total: 0 }, considered: 0, returned: items.length });
@@ -34,12 +59,14 @@ class Cognia {
34
59
  budget;
35
60
  timeoutMs;
36
61
  key;
62
+ tokenProvider;
37
63
  f;
38
64
  onError;
39
65
  constructor(o) {
40
- if (!o.apiKey)
41
- throw new Error("Cognia: apiKey is required");
42
- this.key = o.apiKey;
66
+ if (!o.apiKey && !o.tokenProvider)
67
+ throw new Error("Cognia: apiKey or tokenProvider is required");
68
+ this.key = o.apiKey ?? null;
69
+ this.tokenProvider = o.tokenProvider ?? null;
43
70
  this.baseUrl = (o.baseUrl ?? "https://api.cognia.fun").replace(/\/$/, "");
44
71
  this.scope = o.scope ?? "organization";
45
72
  this.budget = o.budget;
@@ -51,7 +78,11 @@ class Cognia {
51
78
  const ac = new AbortController();
52
79
  const t = timeoutMs ? setTimeout(() => ac.abort(), timeoutMs) : null;
53
80
  try {
54
- const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.1" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
81
+ // delegated auth: the provider is asked on EVERY request; its answer is used once and never stored on the client
82
+ const token = this.tokenProvider ? await this.tokenProvider() : this.key;
83
+ if (!token)
84
+ throw new CogniaError(`Cognia ${method} ${path} → no credential`, 0, null);
85
+ const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${token}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.3" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
55
86
  let j = null;
56
87
  try {
57
88
  j = await r.json();
@@ -4,9 +4,10 @@ exports.provenanceLine = provenanceLine;
4
4
  exports.renderContextBlock = renderContextBlock;
5
5
  /** One provenance line the model can cite and a reader can look up: reference · owner · organization · evidence. */
6
6
  function provenanceLine(it) {
7
- const who = it.owner === "mine" ? "yours" : it.owner === "organization" ? `shared by ${neutral(it.provenance.ownerName ?? "a colleague")}` : `public, by ${neutral(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()}` : "";
7
+ const who = it.owner === "mine" ? "yours" : it.owner === "organization" ? `shared by ${neutral(it.provenance?.ownerName ?? "a colleague")}` : `public, by ${neutral(it.provenance.ownerName ?? "another owner")}`;
8
+ const uses = Number(it.provenance?.verifiedUses ?? 0) || 0;
9
+ const ev = uses > 0 ? `${uses} verified ${uses === 1 ? "use" : "uses"}` : "no verified use yet";
10
+ const mat = it.provenance?.maturity ? ` · ${String(it.provenance.maturity).toLowerCase()}` : "";
10
11
  return `[${it.displayId} · ${who}${mat} · ${ev}]`;
11
12
  }
12
13
  /** The context block placed before the model's instructions. Plain text, deterministic, nothing invented. */
@@ -25,10 +26,11 @@ function renderContextBlock(ctx) {
25
26
  parts.push(`\n[${n}] MEMORY ${provenanceLine(it)}\nTitle: ${neutral(it.title)}\n${neutral(it.body.text)}`);
26
27
  }
27
28
  else {
28
- const steps = (it.body.procedure ?? []).map((s, j) => ` ${j + 1}. ${neutral(s)}`).join("\n");
29
- const when = it.body.trigger?.length ? `When: ${neutral(it.body.trigger.join("; "))}\n` : "";
30
- const not = it.body.doNotApplyWhen?.length ? `Do not apply when: ${neutral(it.body.doNotApplyWhen.join("; "))}\n` : "";
31
- const verify = it.body.verification?.length ? `Verify: ${neutral(it.body.verification.join("; "))}\n` : "";
29
+ const list = (v) => Array.isArray(v) ? v.map(String) : typeof v === "string" && v ? [v] : [];
30
+ const steps = list(it.body?.procedure).map((s, j) => ` ${j + 1}. ${neutral(s)}`).join("\n");
31
+ const when = list(it.body?.trigger).length ? `When: ${neutral(list(it.body?.trigger).join("; "))}\n` : "";
32
+ const not = list(it.body?.doNotApplyWhen).length ? `Do not apply when: ${neutral(list(it.body?.doNotApplyWhen).join("; "))}\n` : "";
33
+ const verify = list(it.body?.verification).length ? `Verify: ${neutral(list(it.body?.verification).join("; "))}\n` : "";
32
34
  parts.push(`\n[${n}] SKILL ${provenanceLine(it)}\nTitle: ${neutral(it.title)}\n${neutral(it.body.summary)}\n${when}${not}Procedure:\n${steps}\n${verify}`.trimEnd());
33
35
  }
34
36
  });
@@ -1,7 +1,10 @@
1
1
  import type { ContextRequest, ContextResponse, ContextResult, OutcomeInput, OutcomeResponse, ExtractResponse, Scope } from "./types.js";
2
2
  export interface CogniaOptions {
3
- /** the agent's credential (cognia_sk_…); never logged, never put in a URL */
4
- apiKey: string;
3
+ /** the agent's credential (cognia_sk_…, a developer or SDK credential); never logged, never put in a URL */
4
+ apiKey?: string;
5
+ /** Delegated auth: called at REQUEST time to obtain the bearer (e.g. an MCP OAuth access token the host already
6
+ * holds); async so the host can refresh. The SDK never caches the token and never puts it in errors or onError. */
7
+ tokenProvider?: () => Promise<string> | string;
5
8
  baseUrl?: string;
6
9
  /** default scope for context(); the identity's grants decide what that means */
7
10
  scope?: Scope;
@@ -33,6 +36,7 @@ export declare class Cognia {
33
36
  } | undefined;
34
37
  readonly timeoutMs: number;
35
38
  private readonly key;
39
+ private readonly tokenProvider;
36
40
  private readonly f;
37
41
  private readonly onError;
38
42
  constructor(o: CogniaOptions);
@@ -5,7 +5,32 @@ export function shapeContext(r) {
5
5
  const o = r;
6
6
  if (typeof o.contextId !== "string" || (o.decision !== "INJECT" && o.decision !== "ABSTAIN"))
7
7
  return null;
8
- const items = Array.isArray(o.items) ? o.items.filter((it) => it && typeof it === "object" && typeof it.id === "string" && (it.kind === "memory" || it.kind === "skill") && typeof it.title === "string" && !!it.body) : [];
8
+ // every item is NORMALISED, not merely filtered (independent E2E F01 retest, 2026-10-07): a missing or null
9
+ // provenance, a Skill body whose arrays arrive as strings, a non-string title — none of these may reach the
10
+ // renderer and throw, because a throw here means the host model is never called (the fail-open contract)
11
+ const arr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : typeof v === "string" && v ? [v] : [];
12
+ const str = (v, d = "") => (typeof v === "string" ? v : d);
13
+ const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
14
+ const q = (p) => (p && typeof p === "object" ? p : {});
15
+ const sOrNull = (v) => (typeof v === "string" ? v : null);
16
+ const items = (Array.isArray(o.items) ? o.items : []).flatMap((raw) => {
17
+ if (!raw || typeof raw !== "object")
18
+ return [];
19
+ const it = raw;
20
+ if (typeof it.id !== "string" || (it.kind !== "memory" && it.kind !== "skill"))
21
+ return [];
22
+ const owner = it.owner === "mine" ? "mine" : it.owner === "organization" ? "organization" : "network";
23
+ const body = q(it.body), p = q(it.provenance);
24
+ const base = { id: it.id, displayId: str(it.displayId, it.id), title: str(it.title, "(untitled)"), owner, score: typeof it.score === "number" && Number.isFinite(it.score) ? it.score : null };
25
+ if (it.kind === "memory") {
26
+ const m = { ...base, kind: "memory", body: { text: str(body.text), kind: sOrNull(body.kind) },
27
+ provenance: { ownerName: sOrNull(p.ownerName), organizationName: sOrNull(p.organizationName), maturity: sOrNull(p.maturity), verifiedUses: num(p.verifiedUses), created: sOrNull(p.created) } };
28
+ return [m];
29
+ }
30
+ const k = { ...base, kind: "skill", body: { summary: str(body.summary), trigger: arr(body.trigger), procedure: arr(body.procedure), verification: arr(body.verification), doNotApplyWhen: arr(body.doNotApplyWhen) },
31
+ provenance: { ownerName: sOrNull(p.ownerName), maturity: str(p.maturity, "unknown"), verifiedUses: num(p.verifiedUses), routeId: sOrNull(p.routeId) } };
32
+ return [k];
33
+ });
9
34
  const decision = o.decision === "INJECT" && items.length ? "INJECT" : "ABSTAIN";
10
35
  const scope = o.scope === "mine" || o.scope === "organization" || o.scope === "network" ? o.scope : "organization";
11
36
  const metrics = (o.metrics && typeof o.metrics === "object" ? o.metrics : { ms: { search: 0, total: 0 }, considered: 0, returned: items.length });
@@ -29,12 +54,14 @@ export class Cognia {
29
54
  budget;
30
55
  timeoutMs;
31
56
  key;
57
+ tokenProvider;
32
58
  f;
33
59
  onError;
34
60
  constructor(o) {
35
- if (!o.apiKey)
36
- throw new Error("Cognia: apiKey is required");
37
- this.key = o.apiKey;
61
+ if (!o.apiKey && !o.tokenProvider)
62
+ throw new Error("Cognia: apiKey or tokenProvider is required");
63
+ this.key = o.apiKey ?? null;
64
+ this.tokenProvider = o.tokenProvider ?? null;
38
65
  this.baseUrl = (o.baseUrl ?? "https://api.cognia.fun").replace(/\/$/, "");
39
66
  this.scope = o.scope ?? "organization";
40
67
  this.budget = o.budget;
@@ -46,7 +73,11 @@ export class Cognia {
46
73
  const ac = new AbortController();
47
74
  const t = timeoutMs ? setTimeout(() => ac.abort(), timeoutMs) : null;
48
75
  try {
49
- const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.1" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
76
+ // delegated auth: the provider is asked on EVERY request; its answer is used once and never stored on the client
77
+ const token = this.tokenProvider ? await this.tokenProvider() : this.key;
78
+ if (!token)
79
+ throw new CogniaError(`Cognia ${method} ${path} → no credential`, 0, null);
80
+ const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${token}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.3" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
50
81
  let j = null;
51
82
  try {
52
83
  j = await r.json();
@@ -1,8 +1,9 @@
1
1
  /** One provenance line the model can cite and a reader can look up: reference · owner · organization · evidence. */
2
2
  export function provenanceLine(it) {
3
- const who = it.owner === "mine" ? "yours" : it.owner === "organization" ? `shared by ${neutral(it.provenance.ownerName ?? "a colleague")}` : `public, by ${neutral(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()}` : "";
3
+ const who = it.owner === "mine" ? "yours" : it.owner === "organization" ? `shared by ${neutral(it.provenance?.ownerName ?? "a colleague")}` : `public, by ${neutral(it.provenance.ownerName ?? "another owner")}`;
4
+ const uses = Number(it.provenance?.verifiedUses ?? 0) || 0;
5
+ const ev = uses > 0 ? `${uses} verified ${uses === 1 ? "use" : "uses"}` : "no verified use yet";
6
+ const mat = it.provenance?.maturity ? ` · ${String(it.provenance.maturity).toLowerCase()}` : "";
6
7
  return `[${it.displayId} · ${who}${mat} · ${ev}]`;
7
8
  }
8
9
  /** The context block placed before the model's instructions. Plain text, deterministic, nothing invented. */
@@ -21,10 +22,11 @@ export function renderContextBlock(ctx) {
21
22
  parts.push(`\n[${n}] MEMORY ${provenanceLine(it)}\nTitle: ${neutral(it.title)}\n${neutral(it.body.text)}`);
22
23
  }
23
24
  else {
24
- const steps = (it.body.procedure ?? []).map((s, j) => ` ${j + 1}. ${neutral(s)}`).join("\n");
25
- const when = it.body.trigger?.length ? `When: ${neutral(it.body.trigger.join("; "))}\n` : "";
26
- const not = it.body.doNotApplyWhen?.length ? `Do not apply when: ${neutral(it.body.doNotApplyWhen.join("; "))}\n` : "";
27
- const verify = it.body.verification?.length ? `Verify: ${neutral(it.body.verification.join("; "))}\n` : "";
25
+ const list = (v) => Array.isArray(v) ? v.map(String) : typeof v === "string" && v ? [v] : [];
26
+ const steps = list(it.body?.procedure).map((s, j) => ` ${j + 1}. ${neutral(s)}`).join("\n");
27
+ const when = list(it.body?.trigger).length ? `When: ${neutral(list(it.body?.trigger).join("; "))}\n` : "";
28
+ const not = list(it.body?.doNotApplyWhen).length ? `Do not apply when: ${neutral(list(it.body?.doNotApplyWhen).join("; "))}\n` : "";
29
+ const verify = list(it.body?.verification).length ? `Verify: ${neutral(list(it.body?.verification).join("; "))}\n` : "";
28
30
  parts.push(`\n[${n}] SKILL ${provenanceLine(it)}\nTitle: ${neutral(it.title)}\n${neutral(it.body.summary)}\n${when}${not}Procedure:\n${steps}\n${verify}`.trimEnd());
29
31
  }
30
32
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognia-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
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
5
  "license": "MIT",
6
6
  "author": "Cognia",