@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/tenant.d.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @egox/contracts — `/egox/tenants/*` wire types (project CRUD).
|
|
3
|
+
*
|
|
4
|
+
* `Tenant` is what the Console calls "Project" — same row, two names.
|
|
5
|
+
* The wire shape preserves `snake_case` field names because that's the
|
|
6
|
+
* actual JSON the backend has always emitted and the console has always
|
|
7
|
+
* consumed. Renaming to camelCase here would be a breaking wire change
|
|
8
|
+
* (every API caller would need to update) and is deliberately out of
|
|
9
|
+
* scope for the additive Phase 9 sweep. If/when we ever do that
|
|
10
|
+
* migration, this is the file the major-version bump touches.
|
|
11
|
+
*
|
|
12
|
+
* Drift cleaned up:
|
|
13
|
+
* - Backend `Tenant` used `Date` for `created_at` / `updated_at` /
|
|
14
|
+
* `attestation_signed_at`; wire and console already used ISO
|
|
15
|
+
* strings. Backend `formatTenant` now emits ISO via the same
|
|
16
|
+
* pattern as Tools and RAG.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Locked vocabularies (mirror DB CHECK constraints)
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Compliance tier (mirrors `egox_tenants.compliance_tier` CHECK
|
|
25
|
+
* constraint, migration 010). Phase 1 services `'standard'` only;
|
|
26
|
+
* the rest are forward-compat for Phase 2.
|
|
27
|
+
*/
|
|
28
|
+
export type ComplianceTier =
|
|
29
|
+
| 'standard'
|
|
30
|
+
| 'pending_review'
|
|
31
|
+
| 'gdpr'
|
|
32
|
+
| 'hipaa'
|
|
33
|
+
| 'enterprise'
|
|
34
|
+
| 'pending_upgrade';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Data residency region (mirrors `egox_tenants.data_residency_region`
|
|
38
|
+
* CHECK constraint, migration 010). Phase 1 deploys only to
|
|
39
|
+
* `'eu-west'`; the rest are forward-compat.
|
|
40
|
+
*/
|
|
41
|
+
export type DataResidencyRegion =
|
|
42
|
+
| 'us-east'
|
|
43
|
+
| 'us-west'
|
|
44
|
+
| 'eu-west'
|
|
45
|
+
| 'eu-central'
|
|
46
|
+
| 'ap-south'
|
|
47
|
+
| 'ap-east';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Operational tenant status (mirrors `egox_tenants.status` CHECK
|
|
51
|
+
* constraint). The legacy `is_active` boolean still exists in
|
|
52
|
+
* Phase 1 and is NOT auto-synced with `status` — both fields are
|
|
53
|
+
* independent until Phase 1.5 deprecates `is_active`.
|
|
54
|
+
*/
|
|
55
|
+
export type TenantStatus =
|
|
56
|
+
| 'active'
|
|
57
|
+
| 'pending_review'
|
|
58
|
+
| 'flagged'
|
|
59
|
+
| 'suspended'
|
|
60
|
+
| 'terminated';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Billing plan (Phase 1.5, two-plan model — migration 035). Drives usage
|
|
64
|
+
* quotas (e.g. monthly /ask limit).
|
|
65
|
+
*
|
|
66
|
+
* - `id_ego` — "Id Ego": free, pay-as-you-go via BYOK, capped limits.
|
|
67
|
+
* Default for self-service signup.
|
|
68
|
+
* - `superego` — "Superego": $20/mo, higher (but still capped) limits.
|
|
69
|
+
*/
|
|
70
|
+
export type TenantPlan = 'id_ego' | 'superego';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Industry classification driving the signup gate (Phase 1.5+).
|
|
74
|
+
* The regulated entries (`'healthcare'`, `'finance'`, `'insurance'`,
|
|
75
|
+
* `'government'`, `'legal'`) auto-route new tenants to
|
|
76
|
+
* `status: 'pending_review'`.
|
|
77
|
+
*/
|
|
78
|
+
export type TenantIndustry =
|
|
79
|
+
| 'saas'
|
|
80
|
+
| 'ecommerce'
|
|
81
|
+
| 'marketing'
|
|
82
|
+
| 'education'
|
|
83
|
+
| 'media'
|
|
84
|
+
| 'travel'
|
|
85
|
+
| 'real_estate'
|
|
86
|
+
| 'productivity'
|
|
87
|
+
| 'other'
|
|
88
|
+
| 'healthcare'
|
|
89
|
+
| 'finance'
|
|
90
|
+
| 'insurance'
|
|
91
|
+
| 'government'
|
|
92
|
+
| 'legal';
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// TenantWire — the canonical row shape (snake_case on the wire)
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Wire-format tenant row. Returned by every `/egox/tenants/*` response.
|
|
100
|
+
*
|
|
101
|
+
* **Field naming.** This is the only wire shape in the contracts
|
|
102
|
+
* package that uses `snake_case`. It's preserved verbatim from the
|
|
103
|
+
* legacy backend → JSON path because the Console already consumes
|
|
104
|
+
* `snake_case` here too. Treat the inconsistency as fixed-by-history,
|
|
105
|
+
* not a goal — net change would be breaking, with zero behavioural
|
|
106
|
+
* value.
|
|
107
|
+
*/
|
|
108
|
+
export interface TenantWire {
|
|
109
|
+
id: string;
|
|
110
|
+
name: string;
|
|
111
|
+
/** URL-safe slug, unique per parent (when `parent_tenant_id` is set). */
|
|
112
|
+
slug: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
is_active: boolean;
|
|
115
|
+
/** ISO 8601 timestamp. */
|
|
116
|
+
created_at: string;
|
|
117
|
+
/** ISO 8601 timestamp. */
|
|
118
|
+
updated_at: string;
|
|
119
|
+
|
|
120
|
+
// Phase 1 compliance posture (migration 010) ----------------------
|
|
121
|
+
compliance_tier: ComplianceTier;
|
|
122
|
+
data_residency_region: DataResidencyRegion;
|
|
123
|
+
/**
|
|
124
|
+
* UUID of the parent tenant when this row is a sub-project. `null`
|
|
125
|
+
* for top-level tenants. Sub-projects share their parent's
|
|
126
|
+
* `compliance_tier` for billing purposes (enforced at the service
|
|
127
|
+
* layer, not the DB).
|
|
128
|
+
*/
|
|
129
|
+
parent_tenant_id: string | null;
|
|
130
|
+
industry: TenantIndustry | null;
|
|
131
|
+
/** ISO 8601 timestamp. `null` until the operator signs an attestation. */
|
|
132
|
+
attestation_signed_at: string | null;
|
|
133
|
+
attestation_version: string | null;
|
|
134
|
+
status: TenantStatus;
|
|
135
|
+
/** Billing plan (migrations 023/035). Drives usage quotas. Default 'id_ego'. */
|
|
136
|
+
plan: TenantPlan;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Request bodies (what the client SENDS)
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Wire body of `POST /egox/tenants`.
|
|
145
|
+
*
|
|
146
|
+
* Notable absences:
|
|
147
|
+
* - `compliance_tier`, `status` — server-derived from `industry`
|
|
148
|
+
* at signup (Phase 1C / Strategy A).
|
|
149
|
+
* Non-regulated industry →
|
|
150
|
+
* `'standard'` + `'active'`;
|
|
151
|
+
* regulated industry (healthcare,
|
|
152
|
+
* finance, insurance, government,
|
|
153
|
+
* legal) → `'pending_review'` on
|
|
154
|
+
* both. Manual tier moves stay an
|
|
155
|
+
* admin operation via
|
|
156
|
+
* `TenantUpdateBody`.
|
|
157
|
+
* - `is_active`, timestamps — server-managed.
|
|
158
|
+
*/
|
|
159
|
+
export interface TenantCreateBody {
|
|
160
|
+
name: string;
|
|
161
|
+
slug?: string;
|
|
162
|
+
description?: string;
|
|
163
|
+
|
|
164
|
+
industry?: TenantIndustry;
|
|
165
|
+
data_residency_region?: DataResidencyRegion;
|
|
166
|
+
parent_tenant_id?: string | null;
|
|
167
|
+
/** ISO 8601 timestamp string the operator submits at signup. */
|
|
168
|
+
attestation_signed_at?: string;
|
|
169
|
+
attestation_version?: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Wire body of `PUT /egox/tenants/:tenantId`. Every field is optional —
|
|
174
|
+
* the backend applies `COALESCE`-style partial updates. `tenantId` is
|
|
175
|
+
* the URL param, not the body.
|
|
176
|
+
*
|
|
177
|
+
* `compliance_tier` and `status` are admin operations; Phase 1 has no
|
|
178
|
+
* role gating on these — Phase 1.5 will introduce admin-only
|
|
179
|
+
* enforcement.
|
|
180
|
+
*/
|
|
181
|
+
export interface TenantUpdateBody {
|
|
182
|
+
name?: string;
|
|
183
|
+
description?: string;
|
|
184
|
+
is_active?: boolean;
|
|
185
|
+
|
|
186
|
+
industry?: TenantIndustry;
|
|
187
|
+
data_residency_region?: DataResidencyRegion;
|
|
188
|
+
parent_tenant_id?: string | null;
|
|
189
|
+
compliance_tier?: ComplianceTier;
|
|
190
|
+
status?: TenantStatus;
|
|
191
|
+
attestation_signed_at?: string;
|
|
192
|
+
attestation_version?: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============================================================================
|
|
196
|
+
// Response data shapes (carried inside `EgoxApiResponse.data`)
|
|
197
|
+
// ============================================================================
|
|
198
|
+
|
|
199
|
+
export interface TenantListResponseData {
|
|
200
|
+
tenants: TenantWire[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface TenantGetResponseData {
|
|
204
|
+
tenant: TenantWire;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Returned by `POST /egox/tenants`. Wrapping the row in `tenant`
|
|
209
|
+
* (rather than returning it raw) matches the rest of the create
|
|
210
|
+
* surfaces and keeps room for additive metadata (creation source,
|
|
211
|
+
* downstream side effects, …) without a major version bump.
|
|
212
|
+
*/
|
|
213
|
+
export interface TenantCreatedResponseData {
|
|
214
|
+
tenant: TenantWire;
|
|
215
|
+
}
|
package/src/thread.d.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @egox/contracts — `/egox/threads/*` and
|
|
3
|
+
* `/egox/tenants/:tenantId/threads/*` wire types.
|
|
4
|
+
*
|
|
5
|
+
* The conversation memory surface. `Thread` rows are produced by every
|
|
6
|
+
* `/ask` turn (via `ThreadService.getOrCreateThread`) and read back
|
|
7
|
+
* via the admin threads list / detail routes.
|
|
8
|
+
*
|
|
9
|
+
* Phase 6's `externalThreadId` already lives in `AskRequestBody` /
|
|
10
|
+
* `AskResponseData` / `AskStreamDoneEvent` (in `ask.d.ts` /
|
|
11
|
+
* `stream.d.ts`). This file lifts the FULL `Thread` and `Message`
|
|
12
|
+
* row shapes — what the threads-admin endpoints return — into the
|
|
13
|
+
* contracts package so the console mirror can't drift.
|
|
14
|
+
*
|
|
15
|
+
* Drift cleaned up:
|
|
16
|
+
* - Backend `Thread` and `Message` used `Date` for `createdAt` /
|
|
17
|
+
* `updatedAt`; wire and console already used ISO strings.
|
|
18
|
+
* `formatThread` and `formatMessage` now emit ISO to match.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Intent } from './envelope';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Locked vocabularies
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/** Role of a message in the conversation turn. */
|
|
28
|
+
export type MessageRole = 'system' | 'user' | 'assistant' | 'tool';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Why a turn was stamped with `failedRecovery: true` by the
|
|
32
|
+
* orchestrator. Mirrors the CHECK constraint on
|
|
33
|
+
* `egox_messages.failure_reason` (migration 014, Phase 5 / Layer 5).
|
|
34
|
+
* Adding a new entry requires a migration + orchestrator change in
|
|
35
|
+
* lockstep — the audit field exists to be queryable, not free-form.
|
|
36
|
+
*/
|
|
37
|
+
export type MessageFailureReason =
|
|
38
|
+
| 'tool_retry_storm'
|
|
39
|
+
| 'iteration_cap'
|
|
40
|
+
| 'max_calls_per_turn'
|
|
41
|
+
| 'unrecoverable_error';
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Supporting shapes
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Tool-call request emitted by an `assistant` message. Shape matches
|
|
49
|
+
* the OpenAI Chat Completions tool-call envelope verbatim — that's
|
|
50
|
+
* what the orchestrator persists. `arguments` is a JSON string the
|
|
51
|
+
* caller must `JSON.parse` if they want the structured value;
|
|
52
|
+
* EgoX doesn't decode it on the wire because it's already
|
|
53
|
+
* round-tripped through the LLM as text.
|
|
54
|
+
*/
|
|
55
|
+
export interface ToolCallWire {
|
|
56
|
+
id: string;
|
|
57
|
+
type: 'function';
|
|
58
|
+
function: {
|
|
59
|
+
name: string;
|
|
60
|
+
arguments: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// ThreadWire — the canonical row shape
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Wire-format thread row. Returned by every threads-admin response and
|
|
70
|
+
* embedded in `ThreadWithMessagesWire`.
|
|
71
|
+
*
|
|
72
|
+
* `externalThreadId` is populated when the caller supplied one on the
|
|
73
|
+
* `/ask` that minted the row (Phase 6); `null` for legacy threads and
|
|
74
|
+
* for any caller that uses only the EgoX UUID path.
|
|
75
|
+
*/
|
|
76
|
+
export interface ThreadWire {
|
|
77
|
+
id: string;
|
|
78
|
+
tenantId: string;
|
|
79
|
+
externalUserId: string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Consumer-supplied conversation identifier (Phase 6). `null` for
|
|
82
|
+
* threads created before the column existed and for any caller
|
|
83
|
+
* that didn't supply one. Uniqueness is enforced per
|
|
84
|
+
* `(tenantId, externalThreadId)` among non-NULL rows by the
|
|
85
|
+
* `idx_egox_threads_tenant_ext` partial unique index — see
|
|
86
|
+
* migration 017.
|
|
87
|
+
*/
|
|
88
|
+
externalThreadId: string | null;
|
|
89
|
+
title: string | null;
|
|
90
|
+
metadata: Record<string, unknown>;
|
|
91
|
+
isActive: boolean;
|
|
92
|
+
/** ISO 8601 timestamp. */
|
|
93
|
+
createdAt: string;
|
|
94
|
+
/** ISO 8601 timestamp. */
|
|
95
|
+
updatedAt: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Thread row plus aggregate stats. Returned by the admin threads list
|
|
100
|
+
* endpoint so the operator-facing table can sort by activity without
|
|
101
|
+
* an N+1 fetch.
|
|
102
|
+
*/
|
|
103
|
+
export interface ThreadWithStatsWire extends ThreadWire {
|
|
104
|
+
messageCount: number;
|
|
105
|
+
totalTokens: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// MessageWire — the canonical message-row shape
|
|
110
|
+
// ============================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Wire-format message row. Carries the full per-turn audit trail —
|
|
114
|
+
* the LLM iteration's prompt/completion token split, the model id,
|
|
115
|
+
* the intent classification, the RAG context that was injected, and
|
|
116
|
+
* the Phase 5 failure-recovery flags.
|
|
117
|
+
*
|
|
118
|
+
* `toolCalls` is populated only on `role: 'assistant'` rows that
|
|
119
|
+
* requested a tool. `toolCallId` + `toolName` are populated only on
|
|
120
|
+
* `role: 'tool'` result rows.
|
|
121
|
+
*/
|
|
122
|
+
export interface MessageWire {
|
|
123
|
+
id: string;
|
|
124
|
+
threadId: string;
|
|
125
|
+
tenantId: string;
|
|
126
|
+
role: MessageRole;
|
|
127
|
+
content: string | null;
|
|
128
|
+
toolCalls: ToolCallWire[] | null;
|
|
129
|
+
toolCallId: string | null;
|
|
130
|
+
toolName: string | null;
|
|
131
|
+
intent: Intent | null;
|
|
132
|
+
ragContext: Record<string, unknown> | null;
|
|
133
|
+
promptTokens: number | null;
|
|
134
|
+
completionTokens: number | null;
|
|
135
|
+
totalTokens: number | null;
|
|
136
|
+
model: string | null;
|
|
137
|
+
/** Echo of `AskRequestBody.metadata` for audit. */
|
|
138
|
+
metadata: Record<string, unknown> | null;
|
|
139
|
+
/**
|
|
140
|
+
* `true` when this row's `content` was deliberately dropped under
|
|
141
|
+
* no-store (metadata-only) persistence — see
|
|
142
|
+
* `AskRequestBody.noStore` and the tenant's `persistMessageContent`
|
|
143
|
+
* setting (#5). Distinguishes an intentional NULL (the conversation
|
|
144
|
+
* text was never stored) from an incidental empty turn. Always
|
|
145
|
+
* `false` for tenants that persist content normally.
|
|
146
|
+
*/
|
|
147
|
+
contentRedacted: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* `true` when this turn triggered a Phase 5 tool-retry-storm
|
|
150
|
+
* backstop (dedup ≥ MAX_DUPLICATE_CALLS_BEFORE_FLAG / per-tool
|
|
151
|
+
* cap / outer iteration cap) or ended on an unrecoverable error
|
|
152
|
+
* class. Always `false` for non-assistant rows. Phase 5 /
|
|
153
|
+
* Layer 5 — drives the Tool Health "bad turns" filter.
|
|
154
|
+
*/
|
|
155
|
+
failedRecovery: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Dominant cause when `failedRecovery === true`. `null`
|
|
158
|
+
* otherwise. See `MessageFailureReason` for the locked
|
|
159
|
+
* vocabulary.
|
|
160
|
+
*/
|
|
161
|
+
failureReason: MessageFailureReason | null;
|
|
162
|
+
/** ISO 8601 timestamp. */
|
|
163
|
+
createdAt: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Returned by the thread-detail endpoint. The full conversation —
|
|
168
|
+
* row + every message in chronological order.
|
|
169
|
+
*/
|
|
170
|
+
export interface ThreadWithMessagesWire {
|
|
171
|
+
thread: ThreadWire;
|
|
172
|
+
messages: MessageWire[];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// Response data shapes (carried inside `EgoxApiResponse.data`)
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
export interface ThreadListResponseData {
|
|
180
|
+
threads: ThreadWithStatsWire[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface ThreadGetResponseData extends ThreadWithMessagesWire {}
|
package/src/tool.d.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @egox/contracts — `/egox/tools/*` + `/egox/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 (`egox-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'` — EgoX 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 EgoX'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 `/egox/tools/*` and
|
|
92
|
+
* `/egox/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 /egox/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 /egox/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 `EgoxApiResponse.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 /egox/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
|
+
}
|