@egox/contracts 0.5.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/LICENSE +201 -0
- package/NOTICE +10 -0
- package/README.md +87 -0
- package/package.json +46 -0
- package/src/api-key.d.ts +155 -0
- package/src/ask.d.ts +225 -0
- package/src/envelope.d.ts +66 -0
- package/src/index.d.ts +87 -0
- package/src/mcp-token.d.ts +133 -0
- package/src/personality.d.ts +275 -0
- package/src/rag.d.ts +188 -0
- package/src/search.d.ts +23 -0
- package/src/stream.d.ts +186 -0
- package/src/tenant.d.ts +215 -0
- package/src/thread.d.ts +183 -0
- package/src/tool.d.ts +269 -0
package/src/ask.d.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @egox/contracts — `/ask` request + response wire types.
|
|
3
|
+
*
|
|
4
|
+
* SSE event types for `/ask/stream` live in `stream.d.ts` — they share
|
|
5
|
+
* the request body via `AskStreamRequestBody` declared here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Intent, JsonSchemaDefinition, ResponseFormat, TokenUsage } from './envelope';
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// /ask — request
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* **Wire** body of `POST /egox/ask` and `POST /egox/ask/stream`.
|
|
16
|
+
*
|
|
17
|
+
* This is exactly what gets serialized into the request body. Consumers
|
|
18
|
+
* (SDK, MCP previews, raw `curl` users) and the backend route handler MUST
|
|
19
|
+
* agree on this shape.
|
|
20
|
+
*
|
|
21
|
+
* Things that look like they should live here but don't:
|
|
22
|
+
* - `tenantId` is sent via `X-Tenant-Id` header (or derived from API key).
|
|
23
|
+
* - `authToken` is sent via `Authorization: Bearer …` header.
|
|
24
|
+
* - `incomingHeaders` is captured from the actual HTTP headers, not the body.
|
|
25
|
+
* These are intentionally NOT body fields — they live where HTTP puts them.
|
|
26
|
+
*/
|
|
27
|
+
export interface AskRequestBody {
|
|
28
|
+
/** End-user's message for this turn. Required, non-empty. */
|
|
29
|
+
message: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Continue an existing conversation by passing back the EgoX-internal
|
|
33
|
+
* UUID returned in a previous response. The two failure modes the
|
|
34
|
+
* legacy single-`threadId` model silently collapsed are now loud:
|
|
35
|
+
* - A well-formed UUID with no matching row → 404 NotFound.
|
|
36
|
+
* - A non-UUID string → 400 InvalidInput.
|
|
37
|
+
* If you want to use your own conversation id (e.g. `conv:abc123`),
|
|
38
|
+
* send it as `externalThreadId` instead and EgoX will mint the
|
|
39
|
+
* UUID for you on first call and re-use it on every subsequent call
|
|
40
|
+
* with the same external id.
|
|
41
|
+
*
|
|
42
|
+
* Mutually exclusive with `externalThreadId` in practice — when both
|
|
43
|
+
* are supplied `threadId` wins for back-compat (the backend logs a
|
|
44
|
+
* debug warning).
|
|
45
|
+
*/
|
|
46
|
+
threadId?: string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* **Opaque** consumer-supplied conversation identifier (Phase 6).
|
|
50
|
+
* Sent on every call in the same conversation so EgoX can resolve
|
|
51
|
+
* to the same thread row without the caller ever round-tripping
|
|
52
|
+
* the EgoX UUID. Shape is yours to choose (`conv:abc123`,
|
|
53
|
+
* `thread:user42:session99`, hashed ids, …); EgoX persists it
|
|
54
|
+
* verbatim and never parses it.
|
|
55
|
+
*
|
|
56
|
+
* Resolution semantics:
|
|
57
|
+
* - First call with this id → EgoX creates a new thread with the
|
|
58
|
+
* id permanently attached. The minted EgoX UUID is also
|
|
59
|
+
* returned on `AskResponseData.threadId` for debugging.
|
|
60
|
+
* - Subsequent calls with the same id → same thread row, no
|
|
61
|
+
* matter what UUID was previously returned.
|
|
62
|
+
*
|
|
63
|
+
* Echoed back unchanged on `AskResponseData.externalThreadId` so
|
|
64
|
+
* the caller can confirm which conversation their request landed in.
|
|
65
|
+
*/
|
|
66
|
+
externalThreadId?: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* **Opaque** end-user identifier from the consumer's POV — used by EgoX
|
|
70
|
+
* for thread association, audit, and pass-through to tools. Any string
|
|
71
|
+
* shape is accepted (UUID, slug, hash, …); EgoX never resolves it
|
|
72
|
+
* against its own admin tables.
|
|
73
|
+
*/
|
|
74
|
+
externalUserId?: string;
|
|
75
|
+
|
|
76
|
+
/** Override the tenant's default response format for this single call. */
|
|
77
|
+
responseFormat?: ResponseFormat;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Required when `responseFormat === 'json_object'`. Injected into the
|
|
81
|
+
* system prompt so the LLM produces conforming JSON.
|
|
82
|
+
*/
|
|
83
|
+
jsonSchema?: JsonSchemaDefinition;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Free-form per-call context forwarded to tool calls and echoed back
|
|
87
|
+
* on the response. Use for tenant/user context your tools need.
|
|
88
|
+
*/
|
|
89
|
+
metadata?: Record<string, unknown>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Per-call override of the tenant's `persist_message_content` default.
|
|
93
|
+
*
|
|
94
|
+
* - `true` → metadata-only for THIS call: EgoX does not persist
|
|
95
|
+
* the user message or the answer text. The thread/
|
|
96
|
+
* message rows still exist (so usage, intent, model,
|
|
97
|
+
* tokens, and the thread correlation survive for
|
|
98
|
+
* analytics) but `content` is `NULL` and the row is
|
|
99
|
+
* flagged `contentRedacted: true`.
|
|
100
|
+
* - `false` → persist text for THIS call **only if the tenant
|
|
101
|
+
* default already allows it**. The tenant's
|
|
102
|
+
* `persist_message_content=false` is a hard floor:
|
|
103
|
+
* `noStore:false` CANNOT re-enable storage on a
|
|
104
|
+
* no-store tenant. It only matters on a tenant that
|
|
105
|
+
* persists by default (where it's a no-op).
|
|
106
|
+
* - omitted → use the tenant's `persist_message_content` setting.
|
|
107
|
+
*
|
|
108
|
+
* Net: a per-call flag can only ever *tighten* (force no-store), never
|
|
109
|
+
* loosen a tenant that has opted out of storing conversations.
|
|
110
|
+
*
|
|
111
|
+
* Built for integrations whose policy is "don't store conversations"
|
|
112
|
+
* (e.g. .collab): set the tenant default to no-store, or pass this
|
|
113
|
+
* per call. When no-store is in effect EgoX keeps no transcript, so
|
|
114
|
+
* multi-turn context must be supplied by the caller (e.g. via a
|
|
115
|
+
* `getConversation` tool) rather than reloaded from EgoX history.
|
|
116
|
+
*/
|
|
117
|
+
noStore?: boolean;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Per-call header map forwarded to the tool the LLM ends up calling,
|
|
121
|
+
* gated by that tool's `forwardHeaders` allowlist. Use this when the
|
|
122
|
+
* value:
|
|
123
|
+
* - is per-end-user (so it can't be baked into the tool's static
|
|
124
|
+
* `headers` config), AND
|
|
125
|
+
* - shares a name with a header EgoX claims for itself (most
|
|
126
|
+
* commonly `Authorization` and `X-API-Key` — both used to
|
|
127
|
+
* authenticate the caller TO EgoX, so they're not free for
|
|
128
|
+
* forwarding via the inbound-HTTP path).
|
|
129
|
+
*
|
|
130
|
+
* Body-sourced entries skip the inbound-HTTP forward blocklist
|
|
131
|
+
* (you opted in by putting them here), but EgoX-internal namespaces
|
|
132
|
+
* (`x-egox-*`, `x-mcp-*`) are still rejected.
|
|
133
|
+
*
|
|
134
|
+
* Header names are case-insensitive on the wire and normalized to
|
|
135
|
+
* the canonical casing on the tool's `forwardHeaders` allowlist
|
|
136
|
+
* before going out on the outbound tool request. Values must be
|
|
137
|
+
* strings; binary headers are not supported.
|
|
138
|
+
*
|
|
139
|
+
* Source precedence on the outbound tool request (later wins):
|
|
140
|
+
* 1. inbound-HTTP forwarded headers (legacy path, blocklist applied)
|
|
141
|
+
* 2. body `toolHeaders` (this field — body wins on name conflicts)
|
|
142
|
+
* 3. tool's static `headers` config (tool-owner default)
|
|
143
|
+
* 4. `Authorization` injected by `authType: 'FORWARD'`
|
|
144
|
+
*/
|
|
145
|
+
toolHeaders?: Record<string, string>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* **Wire** body of `POST /egox/ask/stream`. Identical to `AskRequestBody`
|
|
150
|
+
* plus an opt-in stream-detail flag.
|
|
151
|
+
*
|
|
152
|
+
* `stream`:
|
|
153
|
+
* - `true` (default when omitted) → full thinking timeline (intent, rag.*,
|
|
154
|
+
* tool.*, delta, done).
|
|
155
|
+
* - `'minimal'` → only intent, delta, done, error.
|
|
156
|
+
*/
|
|
157
|
+
export interface AskStreamRequestBody extends AskRequestBody {
|
|
158
|
+
stream?: true | 'minimal';
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Opt-in deep-execution trace for the Console "Your Agent" playground.
|
|
162
|
+
*
|
|
163
|
+
* SECURITY: ignored unless the request is authenticated with a console
|
|
164
|
+
* JWT. API-key and MCP callers cannot enable debug — the route handler
|
|
165
|
+
* silently coerces the flag to `false` for those auth paths.
|
|
166
|
+
*
|
|
167
|
+
* When enabled, the stream additionally emits:
|
|
168
|
+
* - `llm.debug` after each LLM iteration (messages array, tool-call
|
|
169
|
+
* choice, finish reason, model, usage)
|
|
170
|
+
* - `tool.debug` after each tool execution (request method/url/
|
|
171
|
+
* headers/body, response status/headers/body, attempts)
|
|
172
|
+
*
|
|
173
|
+
* Sensitive header values (Authorization, X-Egox-Signature,
|
|
174
|
+
* X-Egox-Timestamp, and anything matching /token|secret|key|auth/i)
|
|
175
|
+
* are redacted to `***` before transmission.
|
|
176
|
+
*
|
|
177
|
+
* Implies `stream: true` (full detail) — `'minimal'` + `debug` is a
|
|
178
|
+
* 400 InvalidInput.
|
|
179
|
+
*/
|
|
180
|
+
debug?: boolean;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// /ask — response
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* **Wire** payload of `POST /egox/ask`, carried inside `EgoxApiResponse.data`.
|
|
189
|
+
*
|
|
190
|
+
* IMPORTANT — the field name is `answer`, not `response`. Both the backend
|
|
191
|
+
* orchestrator's return type and the SDK's decoder reference this interface,
|
|
192
|
+
* which is the structural guard against the two of them ever drifting again.
|
|
193
|
+
*/
|
|
194
|
+
export interface AskResponseData {
|
|
195
|
+
/** Thread the message was appended to (new or existing). */
|
|
196
|
+
threadId: string;
|
|
197
|
+
/**
|
|
198
|
+
* Echo of `AskRequestBody.externalThreadId` when one was supplied —
|
|
199
|
+
* either the consumer-supplied id used to resolve an existing thread,
|
|
200
|
+
* or the id that was just attached to a freshly-created one. Absent
|
|
201
|
+
* when the caller didn't supply an `externalThreadId` (Phase 6).
|
|
202
|
+
*/
|
|
203
|
+
externalThreadId?: string;
|
|
204
|
+
/** The LLM's reply text. Empty string when the model produced no text. */
|
|
205
|
+
answer: string;
|
|
206
|
+
intent: Intent;
|
|
207
|
+
/** Name of the tool the LLM invoked, or `null` if no tool ran. */
|
|
208
|
+
toolUsed: string | null;
|
|
209
|
+
/** How many RAG chunks were injected into context. `0` when RAG was off / unused. */
|
|
210
|
+
ragChunksUsed: number;
|
|
211
|
+
usage: TokenUsage;
|
|
212
|
+
/** Resolved model id, e.g. `gpt-4.1-mini-2025-04-14`. */
|
|
213
|
+
model: string;
|
|
214
|
+
/** Echo of `AskRequestBody.metadata`, unchanged. */
|
|
215
|
+
metadata?: Record<string, unknown>;
|
|
216
|
+
/**
|
|
217
|
+
* Pending high-impact actions awaiting user approval (Path-A, DotCollab #8).
|
|
218
|
+
* Populated (non-empty) when a gated tool returned an `approval_requested`
|
|
219
|
+
* envelope this turn instead of executing — the consumer renders an
|
|
220
|
+
* Approve/Reject card per entry and resolves it out-of-band (e.g. a
|
|
221
|
+
* `.../approvals/:approvalId/resolve` route). Absent/empty otherwise, so
|
|
222
|
+
* consumers that ignore it are unaffected (Path-B native-approvals path).
|
|
223
|
+
*/
|
|
224
|
+
pendingActions?: Array<{ approvalId: string; toolName: string; summary: string }>;
|
|
225
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @egox/contracts — common envelope + shared primitives.
|
|
3
|
+
*
|
|
4
|
+
* Cross-family types that every other contract file ends up importing.
|
|
5
|
+
* Lives in its own file so a future family (RAG, tenants, …) can pull
|
|
6
|
+
* just what it needs without dragging the whole `/ask` surface along.
|
|
7
|
+
*
|
|
8
|
+
* Versioning policy (also in package README, kept here for any reader
|
|
9
|
+
* who lands on this file first):
|
|
10
|
+
* - Additive optional field → minor bump, non-breaking.
|
|
11
|
+
* - Rename / remove → major bump, coordinated release of
|
|
12
|
+
* backend + SDK + MCP + console.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Standard envelope every JSON endpoint wraps its response in.
|
|
17
|
+
* - `status: 'OK'` → success, `data` is present and well-formed.
|
|
18
|
+
* - `status: 'KO'` → failure, `code` + `message` describe the problem.
|
|
19
|
+
*/
|
|
20
|
+
export interface EgoxApiResponse<T> {
|
|
21
|
+
status: 'OK' | 'KO';
|
|
22
|
+
data?: T;
|
|
23
|
+
message?: string;
|
|
24
|
+
code?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What the orchestrator decided this turn was about. Drives prompt /
|
|
29
|
+
* tool selection in the backend; surfaced verbatim to consumers so they
|
|
30
|
+
* can attribute behaviour ("which turns hit RAG?", "which used tools?").
|
|
31
|
+
*/
|
|
32
|
+
export type Intent = 'vanilla' | 'rag' | 'tools' | 'rag_tools';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Output format the LLM was asked to produce.
|
|
36
|
+
* - `'text'` → free-form natural language (default).
|
|
37
|
+
* - `'json_object'` → strict JSON, requires `jsonSchema` on the request.
|
|
38
|
+
*/
|
|
39
|
+
export type ResponseFormat = 'text' | 'json_object';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* JSON Schema fragment used for structured-output (`/ask`'s
|
|
43
|
+
* `responseFormat: 'json_object'`). Intentionally narrow: the wire only
|
|
44
|
+
* accepts an object root with named properties — that's what the LLM
|
|
45
|
+
* constrained-decoders expect.
|
|
46
|
+
*
|
|
47
|
+
* NOT the same as the recursive `JsonSchema` shape used by tool
|
|
48
|
+
* input/output definitions (see `tool.d.ts`). Two different surfaces,
|
|
49
|
+
* two different shapes — don't conflate.
|
|
50
|
+
*/
|
|
51
|
+
export interface JsonSchemaDefinition {
|
|
52
|
+
type: 'object';
|
|
53
|
+
properties: Record<string, unknown>;
|
|
54
|
+
required?: string[];
|
|
55
|
+
description?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Token accounting echoed back on every `/ask` and the `done` SSE frame.
|
|
60
|
+
* Useful for per-call cost attribution on the consumer side.
|
|
61
|
+
*/
|
|
62
|
+
export interface TokenUsage {
|
|
63
|
+
promptTokens: number;
|
|
64
|
+
completionTokens: number;
|
|
65
|
+
totalTokens: number;
|
|
66
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @egox/contracts
|
|
3
|
+
*
|
|
4
|
+
* Wire-format type contracts shared between every EgoX surface that talks
|
|
5
|
+
* over HTTP / SSE — the backend (`backend/egox`), the Node SDK
|
|
6
|
+
* (`@egox/client`), the MCP server (`egox-mcp`), and the console.
|
|
7
|
+
*
|
|
8
|
+
* **Types only.** This file (and every file it re-exports) deliberately
|
|
9
|
+
* exports nothing at runtime so consumers can
|
|
10
|
+
* `import type { ... } from '@egox/contracts'` and pay zero bundle cost,
|
|
11
|
+
* and so a missing path-alias in one tsconfig can never cause a runtime
|
|
12
|
+
* ReferenceError. Per-package runtime constants (e.g.
|
|
13
|
+
* `SDK_RESERVED_HEADERS` in the SDK, `HEADER_FORWARD_BLOCKLIST` on the
|
|
14
|
+
* backend) live next to the code that needs them.
|
|
15
|
+
*
|
|
16
|
+
* **Single source of truth.** Field names and shapes here are
|
|
17
|
+
* authoritative for the wire. Backend response builders, SDK decoders,
|
|
18
|
+
* and MCP previews MUST conform to these types — that is what prevents
|
|
19
|
+
* the kind of silent `answer === undefined` bug we hit when the SDK was
|
|
20
|
+
* reading `.response` while the backend was writing `.answer`.
|
|
21
|
+
*
|
|
22
|
+
* **Layout.** One file per endpoint family (Phase 9):
|
|
23
|
+
* - `envelope.d.ts` — `EgoxApiResponse`, `Intent`, `ResponseFormat`,
|
|
24
|
+
* `JsonSchemaDefinition`, `TokenUsage`.
|
|
25
|
+
* - `ask.d.ts` — `AskRequestBody`, `AskStreamRequestBody`,
|
|
26
|
+
* `AskResponseData`.
|
|
27
|
+
* - `stream.d.ts` — full `AskStreamEvent` discriminated union +
|
|
28
|
+
* every variant.
|
|
29
|
+
* - `tool.d.ts` — `ToolWire`, `ToolCreateBody`, `ToolUpdateBody`,
|
|
30
|
+
* `ToolListResponseData`, `ToolGetResponseData`,
|
|
31
|
+
* `ToolPreview`, vocabularies (`ToolType`,
|
|
32
|
+
* `ToolHttpMethod`, `ToolAuthType`, `ToolSource`),
|
|
33
|
+
* recursive `JsonSchemaWire`.
|
|
34
|
+
* - `rag.d.ts` — `RagDocumentWire`, `RagDocumentCreateBody`,
|
|
35
|
+
* `RagDocumentUpsertByTitleBody`,
|
|
36
|
+
* `RagDocumentListResponseData`,
|
|
37
|
+
* `RagDocumentGetResponseData`,
|
|
38
|
+
* `RagDocumentUpsertResponseData`, vocabularies
|
|
39
|
+
* (`RagDocumentSourceType`, `RagProcessingStatus`,
|
|
40
|
+
* `RagDocumentSource`).
|
|
41
|
+
* - `api-key.d.ts` — `ApiKeyWire`, `ApiKeyCreatedWire`,
|
|
42
|
+
* create / rotate bodies, list / get / created
|
|
43
|
+
* response data, vocabulary (`ApiKeyScope`).
|
|
44
|
+
* - `mcp-token.d.ts` — `McpTokenWire`, `McpTokenCreatedWire`, create
|
|
45
|
+
* body, list / get / created response data,
|
|
46
|
+
* vocabulary (`McpTokenScope`).
|
|
47
|
+
* - `tenant.d.ts` — `TenantWire` (snake_case on the wire — see
|
|
48
|
+
* the file header), create / update bodies,
|
|
49
|
+
* list / get / created response data,
|
|
50
|
+
* vocabularies (`ComplianceTier`,
|
|
51
|
+
* `DataResidencyRegion`, `TenantStatus`,
|
|
52
|
+
* `TenantIndustry`).
|
|
53
|
+
* - `thread.d.ts` — `ThreadWire`, `MessageWire`,
|
|
54
|
+
* `ThreadWithStatsWire`, `ThreadWithMessagesWire`,
|
|
55
|
+
* `ToolCallWire`, list / get response data,
|
|
56
|
+
* vocabularies (`MessageRole`,
|
|
57
|
+
* `MessageFailureReason`).
|
|
58
|
+
* - `personality.d.ts` — Both surfaces share this file. User-level:
|
|
59
|
+
* `PersonalityProfileWire`, `PresetDefinitionWire`,
|
|
60
|
+
* create / update bodies, `ActivePersonalityWire`.
|
|
61
|
+
* Project-scoped: `TenantPersonalityProfileWire`,
|
|
62
|
+
* create / update / clone bodies,
|
|
63
|
+
* `ActiveTenantPersonalityWire`. Shared:
|
|
64
|
+
* `PersonalityTraitsWire`, `PresetType`.
|
|
65
|
+
*
|
|
66
|
+
* This barrel re-exports everything so existing consumers' import paths
|
|
67
|
+
* (`import type { AskResponseData } from '@egox/contracts'`) keep
|
|
68
|
+
* working unchanged. Adding a new family is a single-file change here
|
|
69
|
+
* plus a new `export *` line below.
|
|
70
|
+
*
|
|
71
|
+
* **Versioning.**
|
|
72
|
+
* - Additive optional field → minor bump, non-breaking.
|
|
73
|
+
* - Rename / remove → major bump, coordinated release of
|
|
74
|
+
* backend + SDK + MCP + console.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
export * from './envelope';
|
|
78
|
+
export * from './ask';
|
|
79
|
+
export * from './stream';
|
|
80
|
+
export * from './tool';
|
|
81
|
+
export * from './rag';
|
|
82
|
+
export * from './api-key';
|
|
83
|
+
export * from './mcp-token';
|
|
84
|
+
export * from './tenant';
|
|
85
|
+
export * from './thread';
|
|
86
|
+
export * from './personality';
|
|
87
|
+
export * from './search';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @egox/contracts — `/egox/tenants/:tenantId/mcp-tokens/*` wire types.
|
|
3
|
+
*
|
|
4
|
+
* Per-tenant developer tokens (the `egox_mcp_*` credentials Cursor
|
|
5
|
+
* presents to the EgoX MCP server). Each token is short-lived,
|
|
6
|
+
* tenant-scoped, and revocable.
|
|
7
|
+
*
|
|
8
|
+
* Structurally similar to `api-key.d.ts` but a distinct family:
|
|
9
|
+
* - MCP tokens have a smaller scope vocabulary (no `'ask'`, no
|
|
10
|
+
* `'threads:read'`, no `'admin'`).
|
|
11
|
+
* - MCP tokens carry a `revokedAt` audit field that API keys don't.
|
|
12
|
+
* - MCP tokens have `createdByUserId` provenance (which console
|
|
13
|
+
* user issued it).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Locked vocabularies
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Scopes an MCP token can hold. Mirrors `MCP_SCOPES` in
|
|
22
|
+
* `backend/egox/modules/mcp/token/constants.ts`. The MCP server
|
|
23
|
+
* enforces these in its tool router; the backend re-enforces on
|
|
24
|
+
* every admin route call.
|
|
25
|
+
*/
|
|
26
|
+
export type McpTokenScope =
|
|
27
|
+
| 'tools:read'
|
|
28
|
+
| 'tools:write'
|
|
29
|
+
| 'rag:read'
|
|
30
|
+
| 'rag:write';
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// McpTokenWire — the canonical row shape (no secrets)
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Public-facing MCP token info. Returned by every
|
|
38
|
+
* `GET /egox/tenants/:tenantId/mcp-tokens` and
|
|
39
|
+
* `GET /egox/tenants/:tenantId/mcp-tokens/:tokenId` response.
|
|
40
|
+
*
|
|
41
|
+
* Carries NO secret material — only the display prefix
|
|
42
|
+
* (`egox_mcp_abc12345…`). The raw token is exposed exactly once
|
|
43
|
+
* on create / rotate via `McpTokenCreatedWire`; there is no endpoint
|
|
44
|
+
* that returns it again.
|
|
45
|
+
*
|
|
46
|
+
* Timestamps are ISO 8601 strings (backend's `formatTokenInfo` emits
|
|
47
|
+
* ISO to match the wire — verified by re-anchor in Phase 9.4).
|
|
48
|
+
*/
|
|
49
|
+
export interface McpTokenWire {
|
|
50
|
+
id: string;
|
|
51
|
+
tenantId: string;
|
|
52
|
+
name: string;
|
|
53
|
+
/** Public-display prefix; safe to log. */
|
|
54
|
+
tokenPrefix: string;
|
|
55
|
+
scopes: McpTokenScope[];
|
|
56
|
+
expiresAt: string | null;
|
|
57
|
+
/**
|
|
58
|
+
* When the token was revoked. `null` while active. Distinct from
|
|
59
|
+
* `expiresAt`: revocation is a deliberate user action and
|
|
60
|
+
* recorded immediately; expiration is server-clock natural decay.
|
|
61
|
+
*/
|
|
62
|
+
revokedAt: string | null;
|
|
63
|
+
/** Server-computed: `true` when `expiresAt` is in the past. */
|
|
64
|
+
isExpired: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Server-computed convenience flag — `true` only when
|
|
67
|
+
* `revokedAt === null && !isExpired`. Console badge logic should
|
|
68
|
+
* branch on this rather than re-deriving from the raw fields.
|
|
69
|
+
*/
|
|
70
|
+
isActive: boolean;
|
|
71
|
+
lastUsedAt: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* Total successful authentications using this token. Denormalised
|
|
74
|
+
* counter updated by the MCP auth middleware on every accepted
|
|
75
|
+
* SSE connection.
|
|
76
|
+
*/
|
|
77
|
+
usageCount: number;
|
|
78
|
+
/**
|
|
79
|
+
* Console user who issued the token. `null` for tokens minted
|
|
80
|
+
* via system / migration paths (none today; future-proof).
|
|
81
|
+
*/
|
|
82
|
+
createdByUserId: string | null;
|
|
83
|
+
createdAt: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returned exactly once at creation. The raw `token` is unrecoverable
|
|
88
|
+
* afterwards — only the SHA-256 hash and prefix are persisted
|
|
89
|
+
* server-side. Same one-shot contract as `ApiKeyCreatedWire`.
|
|
90
|
+
*/
|
|
91
|
+
export interface McpTokenCreatedWire {
|
|
92
|
+
/** Raw token — show once, then forget. */
|
|
93
|
+
token: string;
|
|
94
|
+
id: string;
|
|
95
|
+
tenantId: string;
|
|
96
|
+
name: string;
|
|
97
|
+
tokenPrefix: string;
|
|
98
|
+
scopes: McpTokenScope[];
|
|
99
|
+
expiresAt: string | null;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Request bodies (what the client SENDS)
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Wire body of `POST /egox/tenants/:tenantId/mcp-tokens`. `tenantId`
|
|
109
|
+
* is the URL param; `createdByUserId` is derived from the console
|
|
110
|
+
* JWT on the request side.
|
|
111
|
+
*/
|
|
112
|
+
export interface McpTokenCreateBody {
|
|
113
|
+
name: string;
|
|
114
|
+
scopes?: McpTokenScope[];
|
|
115
|
+
/** Optional TTL in days. Server caps at `MAX_EXPIRY_DAYS`. */
|
|
116
|
+
expiresInDays?: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// Response data shapes (carried inside `EgoxApiResponse.data`)
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
export interface McpTokenListResponseData {
|
|
124
|
+
tokens: McpTokenWire[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface McpTokenGetResponseData {
|
|
128
|
+
token: McpTokenWire;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface McpTokenCreatedResponseData {
|
|
132
|
+
token: McpTokenCreatedWire;
|
|
133
|
+
}
|