cognia-sdk 0.1.3 → 0.1.5

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.
@@ -74,24 +74,85 @@ class Cognia {
74
74
  this.f = o.fetch ?? fetch;
75
75
  this.onError = o.onError;
76
76
  }
77
+ /** Every string that reaches the host through an error or a reason has the bearer removed first (independent E2E F28). */
78
+ scrub(v, token) {
79
+ if (!token || token.length < 8)
80
+ return v;
81
+ const re = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
82
+ const walk = (x) => {
83
+ if (typeof x === "string")
84
+ return x.replace(re, "[redacted]");
85
+ if (Array.isArray(x))
86
+ return x.map(walk);
87
+ if (x && typeof x === "object") {
88
+ const o = {};
89
+ for (const [k, val] of Object.entries(x))
90
+ o[k] = walk(val);
91
+ return o;
92
+ }
93
+ return x;
94
+ };
95
+ return walk(v);
96
+ }
77
97
  async call(method, path, body, timeoutMs) {
78
98
  const ac = new AbortController();
79
- const t = timeoutMs ? setTimeout(() => ac.abort(), timeoutMs) : null;
99
+ let timedOut = false;
100
+ const t = timeoutMs ? setTimeout(() => { timedOut = true; ac.abort(); }, timeoutMs) : null;
101
+ let token = null;
80
102
  try {
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;
103
+ // delegated auth: the provider is asked on EVERY request, INSIDE the same deadline as the request itself (independent
104
+ // E2E F27: a pending provider used to block the host model), and its answer is used once, never stored on the client.
105
+ // The deadline exists only while a provider is pending and is always observed (F30: an unobserved rejection in the
106
+ // apiKey path crashed the host process). A provider failure is reported WITHOUT its message (F28: the SDK cannot
107
+ // scrub a bearer it never received) — the host owns that error and can log it itself.
108
+ if (this.tokenProvider) {
109
+ let dt = null;
110
+ const provider = new Promise((resolve, reject) => { try {
111
+ resolve(this.tokenProvider());
112
+ }
113
+ catch (e) {
114
+ reject(e);
115
+ } });
116
+ const deadline = new Promise((_, reject) => { if (timeoutMs)
117
+ dt = setTimeout(() => reject(Object.assign(new Error("tokenProvider timed out"), { name: "AbortError" })), timeoutMs); });
118
+ deadline.catch(() => undefined); // never an unhandled rejection
119
+ try {
120
+ token = await (timeoutMs ? Promise.race([provider, deadline]) : provider);
121
+ }
122
+ catch (e) {
123
+ if (e?.name === "AbortError")
124
+ throw e;
125
+ throw new CogniaError(`Cognia ${method} ${path} → token provider failed`, 0, null);
126
+ }
127
+ finally {
128
+ if (dt)
129
+ clearTimeout(dt);
130
+ }
131
+ }
132
+ else
133
+ token = this.key;
83
134
  if (!token)
84
135
  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 });
136
+ const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${token}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.5" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
86
137
  let j = null;
87
138
  try {
88
139
  j = await r.json();
89
140
  }
90
141
  catch { /* no body */ }
91
142
  if (!r.ok)
92
- throw new CogniaError(`Cognia ${method} ${path} → ${r.status}`, r.status, j);
143
+ throw new CogniaError(`Cognia ${method} ${path} → ${r.status}`, r.status, this.scrub(j, token));
93
144
  return j;
94
145
  }
146
+ catch (e) {
147
+ // whatever the transport or the provider threw: the bearer never travels inside the error. A fresh error is built
148
+ // (a DOMException's message is read-only), keeping the name, status and a scrubbed body.
149
+ const src = e instanceof Error ? e : new Error(String(e));
150
+ const msg = token ? this.scrub(src.message, token) : src.message;
151
+ const out = src instanceof CogniaError ? new CogniaError(msg, src.status, token ? this.scrub(src.body, token) : src.body) : Object.assign(new Error(msg), { name: src.name });
152
+ if (timedOut)
153
+ out.name = "AbortError";
154
+ throw out;
155
+ }
95
156
  finally {
96
157
  if (t)
97
158
  clearTimeout(t);
@@ -40,6 +40,8 @@ export declare class Cognia {
40
40
  private readonly f;
41
41
  private readonly onError;
42
42
  constructor(o: CogniaOptions);
43
+ /** Every string that reaches the host through an error or a reason has the bearer removed first (independent E2E F28). */
44
+ private scrub;
43
45
  private call;
44
46
  /**
45
47
  * The Memories and Skills this identity may use for `task`, or an explicit ABSTAIN. Never throws: on a timeout or a
@@ -69,24 +69,85 @@ export class Cognia {
69
69
  this.f = o.fetch ?? fetch;
70
70
  this.onError = o.onError;
71
71
  }
72
+ /** Every string that reaches the host through an error or a reason has the bearer removed first (independent E2E F28). */
73
+ scrub(v, token) {
74
+ if (!token || token.length < 8)
75
+ return v;
76
+ const re = new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
77
+ const walk = (x) => {
78
+ if (typeof x === "string")
79
+ return x.replace(re, "[redacted]");
80
+ if (Array.isArray(x))
81
+ return x.map(walk);
82
+ if (x && typeof x === "object") {
83
+ const o = {};
84
+ for (const [k, val] of Object.entries(x))
85
+ o[k] = walk(val);
86
+ return o;
87
+ }
88
+ return x;
89
+ };
90
+ return walk(v);
91
+ }
72
92
  async call(method, path, body, timeoutMs) {
73
93
  const ac = new AbortController();
74
- const t = timeoutMs ? setTimeout(() => ac.abort(), timeoutMs) : null;
94
+ let timedOut = false;
95
+ const t = timeoutMs ? setTimeout(() => { timedOut = true; ac.abort(); }, timeoutMs) : null;
96
+ let token = null;
75
97
  try {
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;
98
+ // delegated auth: the provider is asked on EVERY request, INSIDE the same deadline as the request itself (independent
99
+ // E2E F27: a pending provider used to block the host model), and its answer is used once, never stored on the client.
100
+ // The deadline exists only while a provider is pending and is always observed (F30: an unobserved rejection in the
101
+ // apiKey path crashed the host process). A provider failure is reported WITHOUT its message (F28: the SDK cannot
102
+ // scrub a bearer it never received) — the host owns that error and can log it itself.
103
+ if (this.tokenProvider) {
104
+ let dt = null;
105
+ const provider = new Promise((resolve, reject) => { try {
106
+ resolve(this.tokenProvider());
107
+ }
108
+ catch (e) {
109
+ reject(e);
110
+ } });
111
+ const deadline = new Promise((_, reject) => { if (timeoutMs)
112
+ dt = setTimeout(() => reject(Object.assign(new Error("tokenProvider timed out"), { name: "AbortError" })), timeoutMs); });
113
+ deadline.catch(() => undefined); // never an unhandled rejection
114
+ try {
115
+ token = await (timeoutMs ? Promise.race([provider, deadline]) : provider);
116
+ }
117
+ catch (e) {
118
+ if (e?.name === "AbortError")
119
+ throw e;
120
+ throw new CogniaError(`Cognia ${method} ${path} → token provider failed`, 0, null);
121
+ }
122
+ finally {
123
+ if (dt)
124
+ clearTimeout(dt);
125
+ }
126
+ }
127
+ else
128
+ token = this.key;
78
129
  if (!token)
79
130
  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 });
131
+ const r = await this.f(this.baseUrl + path, { method, headers: { authorization: `Bearer ${token}`, "content-type": "application/json", "user-agent": "cognia-sdk/0.1.5" }, body: body === undefined ? undefined : JSON.stringify(body), signal: ac.signal });
81
132
  let j = null;
82
133
  try {
83
134
  j = await r.json();
84
135
  }
85
136
  catch { /* no body */ }
86
137
  if (!r.ok)
87
- throw new CogniaError(`Cognia ${method} ${path} → ${r.status}`, r.status, j);
138
+ throw new CogniaError(`Cognia ${method} ${path} → ${r.status}`, r.status, this.scrub(j, token));
88
139
  return j;
89
140
  }
141
+ catch (e) {
142
+ // whatever the transport or the provider threw: the bearer never travels inside the error. A fresh error is built
143
+ // (a DOMException's message is read-only), keeping the name, status and a scrubbed body.
144
+ const src = e instanceof Error ? e : new Error(String(e));
145
+ const msg = token ? this.scrub(src.message, token) : src.message;
146
+ const out = src instanceof CogniaError ? new CogniaError(msg, src.status, token ? this.scrub(src.body, token) : src.body) : Object.assign(new Error(msg), { name: src.name });
147
+ if (timedOut)
148
+ out.name = "AbortError";
149
+ throw out;
150
+ }
90
151
  finally {
91
152
  if (t)
92
153
  clearTimeout(t);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognia-sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",