@hexis-ai/engram-sdk 0.1.4 → 0.1.6

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.
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Admin SDK: provisions workspaces and API keys against an engram-server
3
+ * `/admin/v1/*` mount. Use from privileged contexts only — the admin token
4
+ * is platform-root and must never reach end-user code paths.
5
+ */
6
+ export interface AdminClientOptions {
7
+ baseUrl: string;
8
+ adminToken: string;
9
+ fetch?: typeof fetch;
10
+ /**
11
+ * Async hook resolved per-request that returns additional headers.
12
+ * Use for short-lived credentials like Cloud Run ID tokens — the platform
13
+ * admin token already travels in `X-Admin-Token`, so the host can put a
14
+ * Google ID token in `Authorization` here without colliding.
15
+ */
16
+ authHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
17
+ }
18
+ export interface Workspace {
19
+ id: string;
20
+ name?: string;
21
+ metadata?: Record<string, unknown>;
22
+ createdAt: string;
23
+ }
24
+ export interface ApiKey {
25
+ id: string;
26
+ workspaceId: string;
27
+ prefix: string;
28
+ name?: string;
29
+ createdAt: string;
30
+ lastUsedAt?: string;
31
+ revokedAt?: string;
32
+ }
33
+ export interface IssuedKey extends ApiKey {
34
+ /** Plaintext key. Stored only by the caller; never retrievable again. */
35
+ raw: string;
36
+ }
37
+ export interface CreateWorkspaceInput {
38
+ id?: string;
39
+ name?: string;
40
+ metadata?: Record<string, unknown>;
41
+ /** Whether to also issue an initial API key. Default true. */
42
+ issueKey?: boolean;
43
+ /** Optional name applied to the initial API key. */
44
+ keyName?: string;
45
+ }
46
+ export interface CreateWorkspaceResult {
47
+ workspace: Workspace;
48
+ /** Present when `issueKey !== false`. */
49
+ key?: IssuedKey;
50
+ }
51
+ export declare class EngramAdmin {
52
+ private readonly baseUrl;
53
+ private readonly adminToken;
54
+ private readonly fetchImpl;
55
+ private readonly authHeaders?;
56
+ constructor(opts: AdminClientOptions);
57
+ createWorkspace(input?: CreateWorkspaceInput): Promise<CreateWorkspaceResult>;
58
+ listWorkspaces(): Promise<Workspace[]>;
59
+ getWorkspace(id: string): Promise<Workspace>;
60
+ deleteWorkspace(id: string): Promise<void>;
61
+ issueKey(workspaceId: string, opts?: {
62
+ name?: string;
63
+ }): Promise<IssuedKey>;
64
+ listKeys(workspaceId: string): Promise<ApiKey[]>;
65
+ revokeKey(workspaceId: string, keyId: string): Promise<void>;
66
+ private request;
67
+ }
68
+ export declare function createAdminClient(opts: AdminClientOptions): EngramAdmin;
package/dist/admin.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Admin SDK: provisions workspaces and API keys against an engram-server
3
+ * `/admin/v1/*` mount. Use from privileged contexts only — the admin token
4
+ * is platform-root and must never reach end-user code paths.
5
+ */
6
+ export class EngramAdmin {
7
+ baseUrl;
8
+ adminToken;
9
+ fetchImpl;
10
+ authHeaders;
11
+ constructor(opts) {
12
+ if (!opts.baseUrl)
13
+ throw new Error("EngramAdmin: baseUrl is required");
14
+ if (!opts.adminToken)
15
+ throw new Error("EngramAdmin: adminToken is required");
16
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
17
+ this.adminToken = opts.adminToken;
18
+ this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
19
+ this.authHeaders = opts.authHeaders;
20
+ }
21
+ async createWorkspace(input = {}) {
22
+ return this.request("POST", "/admin/v1/workspaces", input);
23
+ }
24
+ async listWorkspaces() {
25
+ const r = await this.request("GET", "/admin/v1/workspaces");
26
+ return r.workspaces;
27
+ }
28
+ async getWorkspace(id) {
29
+ return this.request("GET", `/admin/v1/workspaces/${encodeURIComponent(id)}`);
30
+ }
31
+ async deleteWorkspace(id) {
32
+ await this.request("DELETE", `/admin/v1/workspaces/${encodeURIComponent(id)}`);
33
+ }
34
+ async issueKey(workspaceId, opts = {}) {
35
+ return this.request("POST", `/admin/v1/workspaces/${encodeURIComponent(workspaceId)}/keys`, opts);
36
+ }
37
+ async listKeys(workspaceId) {
38
+ const r = await this.request("GET", `/admin/v1/workspaces/${encodeURIComponent(workspaceId)}/keys`);
39
+ return r.keys;
40
+ }
41
+ async revokeKey(workspaceId, keyId) {
42
+ await this.request("DELETE", `/admin/v1/workspaces/${encodeURIComponent(workspaceId)}/keys/${encodeURIComponent(keyId)}`);
43
+ }
44
+ async request(method, path, body) {
45
+ const headers = {
46
+ "content-type": "application/json",
47
+ "x-admin-token": this.adminToken,
48
+ };
49
+ if (this.authHeaders)
50
+ Object.assign(headers, await this.authHeaders());
51
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
52
+ method,
53
+ headers,
54
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
55
+ });
56
+ if (!res.ok) {
57
+ const text = await res.text().catch(() => "");
58
+ throw new Error(`engram-admin ${method} ${path} ${res.status}: ${text}`);
59
+ }
60
+ if (res.status === 204)
61
+ return undefined;
62
+ return (await res.json());
63
+ }
64
+ }
65
+ export function createAdminClient(opts) {
66
+ return new EngramAdmin(opts);
67
+ }
package/dist/client.d.ts CHANGED
@@ -1,6 +1,23 @@
1
1
  import type { ScoredSession, SearchOptions, Session, SessionStep } from "@hexis-ai/engram-core";
2
2
  import { type RefCandidate } from "./extract";
3
- import type { EventBatch, SessionInit } from "./types";
3
+ import type { EventBatch, PersonCreate, PersonInfo, PersonMap, PersonUpdate, SessionInit } from "./types";
4
+ /**
5
+ * Envelope returned by session endpoints. The persons map is deduped
6
+ * across whatever sessions the response carries so display info isn't
7
+ * repeated per row.
8
+ */
9
+ export interface SessionEnvelope {
10
+ session: Session;
11
+ persons: PersonMap;
12
+ }
13
+ export interface SessionListEnvelope {
14
+ sessions: Session[];
15
+ persons: PersonMap;
16
+ }
17
+ export interface SearchEnvelope {
18
+ results: ScoredSession[];
19
+ persons: PersonMap;
20
+ }
4
21
  export interface EngramOptions {
5
22
  apiKey: string;
6
23
  baseUrl: string;
@@ -63,9 +80,8 @@ export interface SearchRequest {
63
80
  };
64
81
  options?: SearchOptions;
65
82
  }
66
- export interface SearchResponse {
67
- results: ScoredSession[];
68
- }
83
+ /** @deprecated Use `SearchEnvelope` (carries `persons` map too). */
84
+ export type SearchResponse = SearchEnvelope;
69
85
  export declare class Engram {
70
86
  private readonly apiKey;
71
87
  private readonly baseUrl;
@@ -78,17 +94,46 @@ export declare class Engram {
78
94
  readonly maxRetries: number;
79
95
  readonly retryBackoffMs: number;
80
96
  constructor(opts: EngramOptions);
97
+ /** Probe identity — returns the workspace the configured key resolves to. */
98
+ me(): Promise<{
99
+ workspaceId: string;
100
+ }>;
81
101
  /** Begin a new session. Returns a handle for buffering events. */
82
102
  startSession(init?: SessionInit): Promise<EngramSession>;
83
- /** Fetch a single Session by id. */
84
- getSession(id: string): Promise<Session>;
85
- /** List recent sessions (server defines defaults & limits). */
103
+ /** Fetch a single session by id, plus the persons map for its participants/viewers. */
104
+ getSession(id: string): Promise<SessionEnvelope>;
105
+ /** List recent sessions plus the deduped persons map across them. */
86
106
  listSessions(opts?: {
87
107
  limit?: number;
88
108
  channel?: string;
89
- }): Promise<Session[]>;
109
+ }): Promise<SessionListEnvelope>;
90
110
  /** Run a search. */
91
- search(req: SearchRequest): Promise<SearchResponse>;
111
+ search(req: SearchRequest): Promise<SearchEnvelope>;
112
+ /** Person operations. The host (e.g. monet) resolves platform identities
113
+ * to person ids before storing sessions; this namespace lets it own the
114
+ * underlying person records (display name + canonical id). */
115
+ readonly persons: {
116
+ /** Allocate a new person id and return the row. */
117
+ create: (input: PersonCreate) => Promise<PersonInfo>;
118
+ /** Upsert at a host-supplied id (backfill / fallback). */
119
+ upsert: (id: string, input: PersonCreate) => Promise<PersonInfo>;
120
+ /** Patch profile fields. Returns 404 if id is unknown. */
121
+ update: (id: string, patch: PersonUpdate) => Promise<PersonInfo>;
122
+ get: (id: string) => Promise<PersonInfo>;
123
+ /** Free-text search (substring across id + display_name). */
124
+ list: (opts?: {
125
+ limit?: number;
126
+ q?: string;
127
+ }) => Promise<{
128
+ persons: PersonInfo[];
129
+ }>;
130
+ /** Sessions where this person appears in participants (default) or viewable_by. */
131
+ sessions: (id: string, opts?: {
132
+ limit?: number;
133
+ channel?: string;
134
+ scope?: "participant" | "viewable";
135
+ }) => Promise<SessionListEnvelope>;
136
+ };
92
137
  /** Internal: ship an event batch to the server. */
93
138
  sendBatch(sessionId: string, batch: EventBatch): Promise<void>;
94
139
  /** Internal accessor used by EngramSession to honor SDK config. */
@@ -109,7 +154,9 @@ export declare class EngramSession {
109
154
  private ended;
110
155
  constructor(engram: Engram, id: string);
111
156
  recordStep(input: RecordStepInput): SessionStep;
112
- addParticipant(identityRef: string): void;
157
+ /** Append a participant to the running session. `personId` must already be
158
+ * resolved by the host (engram-server does not resolve platform identities). */
159
+ addParticipant(personId: string): void;
113
160
  setTitle(title: string): void;
114
161
  /** Mark the session ended and flush buffered events. */
115
162
  end(): Promise<void>;
package/dist/client.js CHANGED
@@ -27,16 +27,20 @@ export class Engram {
27
27
  this.maxRetries = opts.maxRetries ?? 4;
28
28
  this.retryBackoffMs = opts.retryBackoffMs ?? 500;
29
29
  }
30
+ /** Probe identity — returns the workspace the configured key resolves to. */
31
+ async me() {
32
+ return this.request("GET", "/v1/me");
33
+ }
30
34
  /** Begin a new session. Returns a handle for buffering events. */
31
35
  async startSession(init = {}) {
32
36
  const ack = await this.request("POST", "/v1/sessions", init);
33
37
  return new EngramSession(this, ack.id);
34
38
  }
35
- /** Fetch a single Session by id. */
39
+ /** Fetch a single session by id, plus the persons map for its participants/viewers. */
36
40
  async getSession(id) {
37
41
  return this.request("GET", `/v1/sessions/${encodeURIComponent(id)}`);
38
42
  }
39
- /** List recent sessions (server defines defaults & limits). */
43
+ /** List recent sessions plus the deduped persons map across them. */
40
44
  async listSessions(opts = {}) {
41
45
  const qs = new URLSearchParams();
42
46
  if (opts.limit !== undefined)
@@ -50,6 +54,41 @@ export class Engram {
50
54
  async search(req) {
51
55
  return this.request("POST", "/v1/search", req);
52
56
  }
57
+ // ---- Persons ----
58
+ /** Person operations. The host (e.g. monet) resolves platform identities
59
+ * to person ids before storing sessions; this namespace lets it own the
60
+ * underlying person records (display name + canonical id). */
61
+ persons = {
62
+ /** Allocate a new person id and return the row. */
63
+ create: (input) => this.request("POST", "/v1/persons", input),
64
+ /** Upsert at a host-supplied id (backfill / fallback). */
65
+ upsert: (id, input) => this.request("PUT", `/v1/persons/${encodeURIComponent(id)}`, input),
66
+ /** Patch profile fields. Returns 404 if id is unknown. */
67
+ update: (id, patch) => this.request("PATCH", `/v1/persons/${encodeURIComponent(id)}`, patch),
68
+ get: (id) => this.request("GET", `/v1/persons/${encodeURIComponent(id)}`),
69
+ /** Free-text search (substring across id + display_name). */
70
+ list: (opts = {}) => {
71
+ const qs = new URLSearchParams();
72
+ if (opts.limit !== undefined)
73
+ qs.set("limit", String(opts.limit));
74
+ if (opts.q)
75
+ qs.set("q", opts.q);
76
+ const tail = qs.toString();
77
+ return this.request("GET", `/v1/persons${tail ? `?${tail}` : ""}`);
78
+ },
79
+ /** Sessions where this person appears in participants (default) or viewable_by. */
80
+ sessions: (id, opts = {}) => {
81
+ const qs = new URLSearchParams();
82
+ if (opts.limit !== undefined)
83
+ qs.set("limit", String(opts.limit));
84
+ if (opts.channel)
85
+ qs.set("channel", opts.channel);
86
+ if (opts.scope)
87
+ qs.set("scope", opts.scope);
88
+ const tail = qs.toString();
89
+ return this.request("GET", `/v1/persons/${encodeURIComponent(id)}/sessions${tail ? `?${tail}` : ""}`);
90
+ },
91
+ };
53
92
  /** Internal: ship an event batch to the server. */
54
93
  async sendBatch(sessionId, batch) {
55
94
  if (batch.events.length === 0)
@@ -112,12 +151,14 @@ export class EngramSession {
112
151
  this.enqueue(ev);
113
152
  return { tool, resources };
114
153
  }
115
- addParticipant(identityRef) {
154
+ /** Append a participant to the running session. `personId` must already be
155
+ * resolved by the host (engram-server does not resolve platform identities). */
156
+ addParticipant(personId) {
116
157
  const ev = {
117
158
  type: "participant",
118
159
  seq: this.seq++,
119
160
  at: new Date().toISOString(),
120
- identityRef,
161
+ personId,
121
162
  };
122
163
  this.enqueue(ev);
123
164
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { Engram, EngramSession, type EngramOptions, type RecordStepInput, type SearchRequest, type SearchResponse, } from "./client";
1
+ export { Engram, EngramSession, type EngramOptions, type RecordStepInput, type SearchRequest, type SearchResponse, type SearchEnvelope, type SessionEnvelope, type SessionListEnvelope, } from "./client";
2
2
  export { extractReferences, encodeResourceId, type RefCandidate, type ReferenceService, type ReferenceAction, } from "./extract";
3
3
  export { parseToolName, type ParsedToolName } from "./tool-name";
4
4
  export { fetchIdToken, cloudRunIdTokenAuth } from "./id-token";
5
- export type { SessionInit, SessionAck, SessionEvent, StepEvent, ParticipantEvent, TitleEvent, EndEvent, EventBatch, } from "./types";
5
+ export { EngramAdmin, createAdminClient, type AdminClientOptions, type CreateWorkspaceInput, type CreateWorkspaceResult, type Workspace as AdminWorkspace, type ApiKey as AdminApiKey, type IssuedKey as AdminIssuedKey, } from "./admin";
6
+ export type { SessionInit, SessionAck, SessionEvent, StepEvent, ParticipantEvent, TitleEvent, EndEvent, EventBatch, PersonInfo, PersonCreate, PersonUpdate, PersonMap, } from "./types";
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export { Engram, EngramSession, } from "./client";
2
2
  export { extractReferences, encodeResourceId, } from "./extract";
3
3
  export { parseToolName } from "./tool-name";
4
4
  export { fetchIdToken, cloudRunIdTokenAuth } from "./id-token";
5
+ export { EngramAdmin, createAdminClient, } from "./admin";
package/dist/types.d.ts CHANGED
@@ -13,7 +13,18 @@ export interface SessionInit {
13
13
  * (e.g. `chat_ui`, `slack_dm`, `cron:dailyDigest`).
14
14
  */
15
15
  channel?: string;
16
+ /**
17
+ * Person ids that took part. The host (e.g. monet) is responsible for
18
+ * resolving platform identities (slack:U..., email:...) to person ids
19
+ * before calling startSession.
20
+ */
16
21
  participants?: string[];
22
+ /**
23
+ * Person ids that may view this session. Defaults to participants when
24
+ * omitted. engram-server unions this with participants to guarantee
25
+ * `participants ⊆ viewable_by`.
26
+ */
27
+ viewable_by?: string[];
17
28
  }
18
29
  export interface SessionAck {
19
30
  id: string;
@@ -29,7 +40,8 @@ export interface ParticipantEvent {
29
40
  type: "participant";
30
41
  seq: number;
31
42
  at: string;
32
- identityRef: string;
43
+ /** Person id (resolved by the host). */
44
+ personId: string;
33
45
  }
34
46
  export interface TitleEvent {
35
47
  type: "title";
@@ -46,3 +58,27 @@ export type SessionEvent = StepEvent | ParticipantEvent | TitleEvent | EndEvent;
46
58
  export interface EventBatch {
47
59
  events: SessionEvent[];
48
60
  }
61
+ /**
62
+ * Minimal representation engram-server returns for any person. Hosts cache
63
+ * `display_name` for UI rendering; richer profile attributes (role, team,
64
+ * etc.) are intentionally NOT here — those belong to HR / source-of-truth
65
+ * systems the host integrates with.
66
+ */
67
+ export interface PersonInfo {
68
+ id: string;
69
+ display_name: string | null;
70
+ created_at: string;
71
+ updated_at: string;
72
+ }
73
+ export interface PersonCreate {
74
+ display_name?: string;
75
+ }
76
+ export interface PersonUpdate {
77
+ display_name?: string | null;
78
+ }
79
+ /**
80
+ * Map of person_id → PersonInfo, deduped across whatever `Session`s the
81
+ * response contains. Returned at the envelope level so list responses
82
+ * don't repeat the same person info per row.
83
+ */
84
+ export type PersonMap = Record<string, PersonInfo>;
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@hexis-ai/engram-sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Host SDK for engram. Records agent session steps and ships them to an engram server.",
5
- "keywords": ["engram", "agents", "claude", "anthropic", "sdk", "observability"],
5
+ "keywords": [
6
+ "engram",
7
+ "agents",
8
+ "claude",
9
+ "anthropic",
10
+ "sdk",
11
+ "observability"
12
+ ],
6
13
  "homepage": "https://github.com/hexis-ltd/engram#readme",
7
14
  "repository": {
8
15
  "type": "git",
@@ -40,7 +47,7 @@
40
47
  "type-check": "tsc --noEmit"
41
48
  },
42
49
  "dependencies": {
43
- "@hexis-ai/engram-core": "^0.1.4"
50
+ "@hexis-ai/engram-core": "^0.1.5"
44
51
  },
45
52
  "publishConfig": {
46
53
  "access": "public"