@ego-z/contracts 0.11.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/src/tool.d.ts ADDED
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @ego-z/contracts — `/egoz/tools/*` + `/egoz/mcp-admin/tools/*` wire types.
3
+ *
4
+ * Three independent consumers used to hand-mirror these shapes:
5
+ * - the backend (`modules/tool/interfaces.ts`)
6
+ * - the MCP server (`egoz-mcp/src/backend-client.ts` — `BackendTool`)
7
+ * - the console (`console/src/types/models.ts` — `Tool`)
8
+ * Each drift was one rename away from a silent runtime bug — the same
9
+ * shape of failure that produced the `answer === undefined` regression
10
+ * on `/ask`. Phase 9.1 unifies them under `ToolWire`.
11
+ *
12
+ * **Wire shape vs. backend in-process shape.** Timestamps are typed as
13
+ * `string` here because that's what JSON serialization produces (Fastify
14
+ * calls `Date.prototype.toJSON()` on the way out). The backend's
15
+ * in-process `Tool` type re-exports `ToolWire` and its formatter now
16
+ * emits ISO strings to match — no consumer of `tool.createdAt` in the
17
+ * backend treats it as a `Date`, so there's no plumbing cost.
18
+ */
19
+
20
+ // ============================================================================
21
+ // Locked vocabularies
22
+ // ============================================================================
23
+
24
+ /**
25
+ * What kind of upstream API this tool wraps. Locked because the
26
+ * orchestrator branches on it (REST → fetch, GRAPHQL → POST gql_query).
27
+ * Adding a value is a one-edit change across backend + MCP + console
28
+ * because they all reference `ToolType` from this file.
29
+ */
30
+ export type ToolType = 'REST' | 'GRAPHQL';
31
+
32
+ /** HTTP method for REST tools. Locked — the backend allows only these. */
33
+ export type ToolHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
34
+
35
+ /**
36
+ * How the tool authenticates upstream.
37
+ * - `'FORWARD'` — EgoZ injects the caller's `authToken` into the
38
+ * outbound request as `Authorization: Bearer …`.
39
+ * - `'NONE'` — no auth header is injected; tool-static `headers`
40
+ * are still applied.
41
+ */
42
+ export type ToolAuthType = 'FORWARD' | 'NONE';
43
+
44
+ /**
45
+ * Provenance of the tool definition.
46
+ * - `'manual'` — created in the Console.
47
+ * - `'mcp'` — created via the MCP server (Cursor / 3rd-party MCP
48
+ * client). Mutating an `mcp` tool from the Console
49
+ * requires an explicit override.
50
+ */
51
+ export type ToolSource = 'manual' | 'mcp';
52
+
53
+ // ============================================================================
54
+ // JSON Schema (recursive, for tool input/output shapes)
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Recursive JSON Schema fragment used by tool `inputSchema` /
59
+ * `outputSchema`. Distinct from `JsonSchemaDefinition` in `envelope.d.ts`
60
+ * (which is the narrow root-must-be-object shape used for `/ask`'s
61
+ * `responseFormat: 'json_object'`).
62
+ *
63
+ * Intentionally narrow vs. the full JSON Schema 2020-12 surface — only
64
+ * what EgoZ's LLM-side adapter actually consumes. Adding a field here
65
+ * requires updating the adapter in `modules/tool/index.ts` in the
66
+ * same PR.
67
+ */
68
+ export interface JsonSchemaWire {
69
+ type: string;
70
+ properties?: Record<string, JsonSchemaPropertyWire>;
71
+ required?: string[];
72
+ additionalProperties?: boolean;
73
+ items?: JsonSchemaWire;
74
+ description?: string;
75
+ }
76
+
77
+ export interface JsonSchemaPropertyWire {
78
+ type: string;
79
+ description?: string;
80
+ enum?: string[];
81
+ items?: JsonSchemaPropertyWire;
82
+ properties?: Record<string, JsonSchemaPropertyWire>;
83
+ required?: string[];
84
+ }
85
+
86
+ // ============================================================================
87
+ // ToolWire — the canonical row shape
88
+ // ============================================================================
89
+
90
+ /**
91
+ * Wire-format Tool row. Returned by every `/egoz/tools/*` and
92
+ * `/egoz/mcp-admin/tools/*` response that produces a Tool.
93
+ *
94
+ * Backend in-process `Tool` is an alias of this type — including the
95
+ * `tenantId` field, which is on the row even though the API caller
96
+ * never sends it (it's derived from API key / JWT context on the
97
+ * request side, but echoed on responses for audit).
98
+ */
99
+ export interface ToolWire {
100
+ id: string;
101
+ /**
102
+ * Owning tenant. Present on every response for audit; the request
103
+ * side never carries it (the backend derives tenant from the API
104
+ * key context).
105
+ */
106
+ tenantId: string;
107
+ toolName: string;
108
+ toolDescription: string;
109
+ apiEndpoint: string;
110
+ toolType: ToolType;
111
+ httpMethod: ToolHttpMethod;
112
+ inputSchema: JsonSchemaWire;
113
+ outputSchema: JsonSchemaWire;
114
+ headers: Record<string, string>;
115
+ authType: ToolAuthType;
116
+ retryAttempts: number;
117
+ retryDelayMs: number;
118
+ enabled: boolean;
119
+ /** GraphQL query/mutation string. Required when `toolType === 'GRAPHQL'`. */
120
+ gqlQuery: string | null;
121
+ /** Example input values for LLM reference. */
122
+ exampleInput: Record<string, unknown> | null;
123
+ /** Example output values for LLM reference. */
124
+ exampleOutput: Record<string, unknown> | null;
125
+ /** Total LLM-initiated invocations (denormalized counter). */
126
+ callCount: number;
127
+ successCount: number;
128
+ failureCount: number;
129
+ /**
130
+ * ISO 8601 timestamp of the most recent invocation. `null` when the
131
+ * tool has never been called. The backend in-process formatter
132
+ * emits ISO strings here so wire and runtime shapes agree.
133
+ */
134
+ lastCalledAt: string | null;
135
+ source: ToolSource;
136
+ /**
137
+ * Lowercase header-name allowlist. Headers from the inbound `/ask`
138
+ * request whose name matches an entry are forwarded verbatim to
139
+ * the tool endpoint. Empty array (default) = no inbound header is
140
+ * forwarded — preserves the original safe behavior.
141
+ *
142
+ * Forbidden names (`Cookie`, `Host`, `Authorization`, …) are
143
+ * stripped defensively at execution time even if accidentally
144
+ * listed here.
145
+ */
146
+ forwardHeaders: string[];
147
+ /**
148
+ * Hard cap on how many times this tool may be EXECUTED inside a
149
+ * single `/ask` turn. Dedup cache hits don't count — only real
150
+ * upstream calls. When exceeded, the orchestrator returns a
151
+ * synthetic refusal message to the LLM. Range 1-50, default 5
152
+ * (Phase 5 / Layer 2).
153
+ */
154
+ maxCallsPerTurn: number;
155
+ /** ISO 8601 timestamp. */
156
+ createdAt: string;
157
+ /** ISO 8601 timestamp. */
158
+ updatedAt: string;
159
+ }
160
+
161
+ // ============================================================================
162
+ // Request bodies (what the client SENDS)
163
+ // ============================================================================
164
+
165
+ /**
166
+ * Wire body of `POST /egoz/tools`. Notable absences:
167
+ * - `tenantId` — derived from API key context / JWT.
168
+ * - `enabled` — new tools are always created `enabled: true`.
169
+ * - counters / `lastCalledAt` — server-managed.
170
+ * - timestamps — server-managed.
171
+ */
172
+ export interface ToolCreateBody {
173
+ toolName: string;
174
+ toolDescription: string;
175
+ apiEndpoint: string;
176
+ toolType?: ToolType;
177
+ httpMethod?: ToolHttpMethod;
178
+ inputSchema: JsonSchemaWire;
179
+ outputSchema?: JsonSchemaWire;
180
+ headers?: Record<string, string>;
181
+ authType?: ToolAuthType;
182
+ retryAttempts?: number;
183
+ retryDelayMs?: number;
184
+ /** GraphQL query/mutation. Required when `toolType === 'GRAPHQL'`. */
185
+ gqlQuery?: string;
186
+ exampleInput?: Record<string, unknown>;
187
+ exampleOutput?: Record<string, unknown>;
188
+ /** Provenance. Defaults to `'manual'`. */
189
+ source?: ToolSource;
190
+ forwardHeaders?: string[];
191
+ /** Per-turn execution cap (1-50, default 5). */
192
+ maxCallsPerTurn?: number;
193
+ }
194
+
195
+ /**
196
+ * Wire body of `PUT /egoz/tools/:toolId`. Every field is optional —
197
+ * unspecified fields are left untouched (`COALESCE` semantics).
198
+ * `toolId` lives in the URL, not the body; `tenantId` is derived
199
+ * from the auth context.
200
+ */
201
+ export interface ToolUpdateBody {
202
+ toolName?: string;
203
+ toolDescription?: string;
204
+ apiEndpoint?: string;
205
+ toolType?: ToolType;
206
+ httpMethod?: ToolHttpMethod;
207
+ inputSchema?: JsonSchemaWire;
208
+ outputSchema?: JsonSchemaWire;
209
+ headers?: Record<string, string>;
210
+ authType?: ToolAuthType;
211
+ retryAttempts?: number;
212
+ retryDelayMs?: number;
213
+ enabled?: boolean;
214
+ gqlQuery?: string;
215
+ exampleInput?: Record<string, unknown>;
216
+ exampleOutput?: Record<string, unknown>;
217
+ source?: ToolSource;
218
+ forwardHeaders?: string[];
219
+ maxCallsPerTurn?: number;
220
+ }
221
+
222
+ // ============================================================================
223
+ // Response data shapes (carried inside `EgozApiResponse.data`)
224
+ // ============================================================================
225
+
226
+ export interface ToolListResponseData {
227
+ tools: ToolWire[];
228
+ }
229
+
230
+ export interface ToolGetResponseData {
231
+ tool: ToolWire;
232
+ }
233
+
234
+ // ============================================================================
235
+ // MCP-admin tool preview (`POST /egoz/mcp-admin/tools/:name/preview`)
236
+ // ============================================================================
237
+
238
+ /**
239
+ * Single field diff entry for a tool-upsert preview. `oldValue` is
240
+ * `undefined` on a fresh create.
241
+ */
242
+ export interface ToolPreviewDiffEntry {
243
+ field: string;
244
+ oldValue: unknown;
245
+ newValue: unknown;
246
+ }
247
+
248
+ /**
249
+ * Response payload of the MCP "preview before upsert" endpoint.
250
+ *
251
+ * - `action` — what an unforced PUT would do.
252
+ * - `requiresForce` — true when overwriting a `'manual'` tool
253
+ * from MCP; the upsert refuses without
254
+ * `force: true`.
255
+ * - `existingSource` — provenance of the row that would be
256
+ * overwritten; null on a fresh create.
257
+ * - `validationErrors` — flat strings; empty array means the body
258
+ * is valid as-is.
259
+ * - `diff` — only the fields that would change.
260
+ */
261
+ export interface ToolPreview {
262
+ toolName: string;
263
+ action: 'create' | 'update' | 'noop';
264
+ exists: boolean;
265
+ requiresForce: boolean;
266
+ existingSource: ToolSource | null;
267
+ validationErrors: string[];
268
+ diff: ToolPreviewDiffEntry[];
269
+ }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * @ego-z/contracts — `/egoz/tenants/:tenantId/learning/*` wire types
3
+ * (Phase 15 / exURM — per-end-user learning).
4
+ *
5
+ * The Console calls this surface **Learning**; the backend domain is
6
+ * `user-memory` (table `egoz_user_memory`). One HYBRID row shape covers both
7
+ * kinds via a `kind` discriminator:
8
+ * - `profile` — a structured attribute (`attrKey` = `content`, e.g.
9
+ * `size` = `38`). Always injected when approved.
10
+ * - `memory` — a free-text snippet (`attrKey` null), embedded server-side
11
+ * and similarity-retrieved. The `embedding` itself NEVER crosses the wire.
12
+ *
13
+ * GOVERNANCE (locked): review-before-use. Rows land `status: 'pending'`; only
14
+ * `'approved'` rows are ever injected into an `/ask` prompt. Opt-in per tenant
15
+ * (`learning_enabled`, default false); no capture under no-store.
16
+ *
17
+ * ── Admin routes (all guarded by tenant-owner auth) ─────────────────────────
18
+ * GET /egoz/tenants/:tenantId/learning/users
19
+ * → LearningUsersResponseData (roster + pending badge total + flag)
20
+ * GET /egoz/tenants/:tenantId/learning/users/:externalUserId?status=
21
+ * → LearningUserFactsResponseData (profile + memories, one flat list)
22
+ * PATCH /egoz/tenants/:tenantId/learning/facts/:factId
23
+ * body UpdateUserMemoryFactBody → UserMemoryFactResponseData
24
+ * POST /egoz/tenants/:tenantId/learning/facts/:factId/approve
25
+ * → UserMemoryFactResponseData
26
+ * POST /egoz/tenants/:tenantId/learning/facts/:factId/reject
27
+ * → UserMemoryFactResponseData
28
+ * DELETE /egoz/tenants/:tenantId/learning/facts/:factId
29
+ * → UserMemoryFactResponseData (the deleted row, for optimistic UI)
30
+ * DELETE /egoz/tenants/:tenantId/learning/users/:externalUserId
31
+ * → ForgetUserResponseData (right-to-be-forgotten)
32
+ * GET /egoz/tenants/:tenantId/learning/analytics
33
+ * → LearningAnalyticsResponseData (per-tenant adoption summary)
34
+ * (the on/off toggle reuses the existing tenant-config update with
35
+ * `learningEnabled` — no dedicated route here.)
36
+ *
37
+ * **Versioning.** Additive new family → minor bump (0.5.3 → 0.6.0).
38
+ */
39
+
40
+ // ============================================================================
41
+ // Locked vocabularies (mirror DB CHECK constraints — migration 042)
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Hybrid discriminator (mirrors `egoz_user_memory.kind` CHECK, migration 042).
46
+ * - `profile` — structured attribute, always injected when approved.
47
+ * - `memory` — free-text snippet, embedded + similarity-retrieved.
48
+ * Adding a value requires a migration + this type in lockstep.
49
+ */
50
+ export type UserMemoryKind = 'profile' | 'memory';
51
+
52
+ /**
53
+ * Review lifecycle (mirrors `egoz_user_memory.status` CHECK, migration 042).
54
+ * Only `'approved'` is ever read on the `/ask` path.
55
+ */
56
+ export type UserMemoryStatus = 'pending' | 'approved' | 'rejected';
57
+
58
+ /**
59
+ * Provenance (mirrors `egoz_user_memory.source` CHECK, migration 042).
60
+ * `'agent_tool'` = proposed by the built-in `remember_fact` tool (MVP; the
61
+ * only source until a manual-add path lands).
62
+ */
63
+ export type UserMemorySource = 'agent_tool';
64
+
65
+ // ============================================================================
66
+ // UserMemoryWire — the canonical row shape
67
+ // ============================================================================
68
+
69
+ /**
70
+ * Wire-format learned-fact row. Returned by the learning detail + mutation
71
+ * endpoints. The `embedding` column is intentionally omitted — it never
72
+ * crosses the wire.
73
+ */
74
+ export interface UserMemoryWire {
75
+ id: string;
76
+ tenantId: string;
77
+ /** The opaque end-user scope key from `/ask` (`externalUserId`). */
78
+ externalUserId: string;
79
+ kind: UserMemoryKind;
80
+ /**
81
+ * The attribute name for `kind: 'profile'` (e.g. `'size'`); `null` for
82
+ * `kind: 'memory'`.
83
+ */
84
+ attrKey: string | null;
85
+ /** The attribute value (`profile`) or the memory snippet (`memory`). */
86
+ content: string;
87
+ status: UserMemoryStatus;
88
+ source: UserMemorySource;
89
+ /** Thread that produced this fact (audit); `null` when unknown / no-store. */
90
+ originThreadId: string | null;
91
+ /** Message that produced this fact (audit); `null` when unknown / no-store. */
92
+ originMessageId: string | null;
93
+ /** Optional model-reported confidence in `[0, 1]`; `null` when not given. */
94
+ confidence: number | null;
95
+ /** ISO 8601 timestamp. */
96
+ createdAt: string;
97
+ /** ISO 8601 timestamp. */
98
+ updatedAt: string;
99
+ /** ISO 8601 timestamp; `null` until reviewed (approved/rejected/edited). */
100
+ reviewedAt: string | null;
101
+ /** User id who reviewed; `null` while pending. */
102
+ reviewedBy: string | null;
103
+ /**
104
+ * Whether the Presidio pass flagged any PII/PHI in this fact's text at
105
+ * propose time (Phase C). The review UI shows an amber "possible PII" chip
106
+ * when true so the owner scrutinises before approving. Absent/false when
107
+ * none found or Presidio isn't configured. Additive — older consumers ignore it.
108
+ */
109
+ piiFlagged?: boolean;
110
+ /**
111
+ * The distinct PII/PHI entity TYPES Presidio flagged (e.g. `'PERSON'`,
112
+ * `'EMAIL_ADDRESS'`, `'US_SSN'`), strongest-confidence first. Types only —
113
+ * never the matched text. Empty/absent when `piiFlagged` is false. Shown in
114
+ * the chip's tooltip. Additive.
115
+ */
116
+ piiEntities?: string[];
117
+ }
118
+
119
+ // ============================================================================
120
+ // Roster summary — one row per external user
121
+ // ============================================================================
122
+
123
+ /**
124
+ * Per-end-user roster entry for the Learning list screen. Aggregated over that
125
+ * user's `egoz_user_memory` rows — no per-fact fetch needed for the table.
126
+ */
127
+ export interface UserMemoryUserSummaryWire {
128
+ externalUserId: string;
129
+ /** Count of `status: 'approved'` facts (profile + memory). */
130
+ approvedCount: number;
131
+ /** Count of `status: 'pending'` facts — drives the review badge. */
132
+ pendingCount: number;
133
+ /**
134
+ * Most recent learning activity for this user (max `created_at` across
135
+ * their facts), ISO 8601; `null` if somehow empty. Labelled "Last activity"
136
+ * in the UI — it is fact activity, not a true last-seen from threads.
137
+ */
138
+ lastActivityAt: string | null;
139
+ }
140
+
141
+ // ============================================================================
142
+ // Request bodies (what the client SENDS)
143
+ // ============================================================================
144
+
145
+ /**
146
+ * Body of `PATCH …/learning/facts/:factId`. Owner edits a proposed/approved
147
+ * fact before or after approving it. Both fields optional (partial update);
148
+ * `attrKey` only meaningful for `kind: 'profile'`.
149
+ */
150
+ export interface UpdateUserMemoryFactBody {
151
+ content?: string;
152
+ attrKey?: string | null;
153
+ }
154
+
155
+ // ============================================================================
156
+ // Response data shapes (carried inside `EgozApiResponse.data`)
157
+ // ============================================================================
158
+
159
+ /**
160
+ * `GET …/learning/users`. The roster plus the flag state (so the list screen
161
+ * can render the on/off Switch without a second call) and the tenant-wide
162
+ * pending total for the sidebar badge.
163
+ */
164
+ export interface LearningUsersResponseData {
165
+ /** Current per-tenant opt-in state (`egoz_tenant_configs.learning_enabled`). */
166
+ learningEnabled: boolean;
167
+ /** Sum of `pendingCount` across all users — the sidebar/nav badge. */
168
+ totalPending: number;
169
+ users: UserMemoryUserSummaryWire[];
170
+ }
171
+
172
+ /**
173
+ * `GET …/learning/users/:externalUserId`. All of the user's facts as one flat
174
+ * list — partition client-side: review queue = `status === 'pending'`;
175
+ * approved profile = `kind === 'profile' && status === 'approved'`; approved
176
+ * memories = `kind === 'memory' && status === 'approved'`. When the request
177
+ * carried `?status=`, `facts` is pre-filtered to that status.
178
+ */
179
+ export interface LearningUserFactsResponseData {
180
+ externalUserId: string;
181
+ facts: UserMemoryWire[];
182
+ }
183
+
184
+ /**
185
+ * Returned by every single-fact mutation (`PATCH`, `approve`, `reject`,
186
+ * `DELETE …/facts/:factId`) — the affected row for optimistic UI reconciliation.
187
+ * On delete this is the row as it was just before removal.
188
+ */
189
+ export interface UserMemoryFactResponseData {
190
+ fact: UserMemoryWire;
191
+ }
192
+
193
+ /**
194
+ * `DELETE …/learning/users/:externalUserId` (right-to-be-forgotten). Reports
195
+ * how many facts were purged so the UI can confirm.
196
+ */
197
+ export interface ForgetUserResponseData {
198
+ externalUserId: string;
199
+ deletedCount: number;
200
+ }
201
+
202
+ /**
203
+ * `GET …/learning/analytics` (Phase C). Per-tenant learning adoption summary —
204
+ * single-query aggregates over `egoz_user_memory`, scoped to the tenant. Powers
205
+ * the Console analytics stat tiles.
206
+ */
207
+ export interface LearningAnalyticsResponseData {
208
+ /** Current per-tenant opt-in state (`egoz_tenant_configs.learning_enabled`). */
209
+ learningEnabled: boolean;
210
+ /** Distinct external users that have ≥1 learned fact. */
211
+ totalUsers: number;
212
+ /** Fact counts by review status. */
213
+ facts: {
214
+ total: number;
215
+ pending: number;
216
+ approved: number;
217
+ rejected: number;
218
+ };
219
+ /** Fact counts by kind (profile vs semantic memory). */
220
+ byKind: {
221
+ profile: number;
222
+ memory: number;
223
+ };
224
+ /** Facts whose text the Presidio pass flagged for possible PII/PHI. */
225
+ piiFlaggedCount: number;
226
+ }