@m6d/cortex-server 2.0.1 → 2.1.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/README.md +10 -0
- package/contracts/interactive.ts +53 -0
- package/contracts/runtime.ts +22 -0
- package/contracts/wire.ts +32 -0
- package/dist/contracts/interactive.d.ts +39 -0
- package/dist/contracts/runtime.d.ts +80 -0
- package/dist/contracts/wire.d.ts +30 -0
- package/dist/src/lib/adapters/database/index.d.ts +7 -0
- package/dist/src/lib/adapters/database/message-content.d.ts +2 -0
- package/dist/src/lib/adapters/database/mssql/index.d.ts +1 -0
- package/dist/src/lib/adapters/database/mssql/messages.d.ts +1 -0
- package/dist/src/lib/adapters/database/postgres/index.d.ts +1 -0
- package/dist/src/lib/adapters/database/postgres/messages.d.ts +1 -0
- package/dist/src/lib/ai/cc-runtime.d.ts +2 -1
- package/dist/src/lib/ai/interactive.d.ts +62 -0
- package/dist/src/lib/ai/turn-tools.d.ts +14 -0
- package/dist/src/lib/cc/client.d.ts +24 -0
- package/dist/src/lib/cc/registry.d.ts +14 -1
- package/dist/src/lib/cc/types.d.ts +1 -1
- package/package.json +5 -2
- package/src/lib/adapters/database/index.ts +12 -0
- package/src/lib/adapters/database/mssql/messages.ts +28 -1
- package/src/lib/adapters/database/postgres/messages.ts +22 -1
- package/src/lib/ai/cc-runtime.ts +16 -1
- package/src/lib/ai/index.ts +34 -5
- package/src/lib/ai/interactive.ts +364 -0
- package/src/lib/ai/tools/search-tools.tool.ts +6 -2
- package/src/lib/ai/turn-tools.ts +23 -0
- package/src/lib/cc/client.ts +5 -1
- package/src/lib/cc/format.ts +5 -2
- package/src/lib/cc/registry.ts +16 -2
- package/src/lib/cc/types.ts +1 -0
- package/src/lib/routes/chat.ts +23 -0
package/README.md
CHANGED
|
@@ -64,6 +64,16 @@ Only `database`, `model`, and `agents` are required for a minimal setup. `storag
|
|
|
64
64
|
|
|
65
65
|
Each key in `agents` becomes a route prefix (e.g. `assistant` → `/agents/assistant/...`). Agents can define per-agent `systemPrompt`, `tools`, `backendFetch`, `loadSessionData`, `resolveRequestContext`, and lifecycle hooks (`onToolCall`, `onStreamFinish`).
|
|
66
66
|
|
|
67
|
+
## Interactive tools
|
|
68
|
+
|
|
69
|
+
Control-Center tools published with kind "interactive" are declared to the
|
|
70
|
+
model as client-executed tools: the call streams to the widget, the run parks,
|
|
71
|
+
and `POST /chat/:chatId/tools/:toolCallId/initiate` hands the widget its embed
|
|
72
|
+
payload (never the model). When the widget answers, the server re-establishes
|
|
73
|
+
the result through the tool's verify endpoint and applies the tool's result
|
|
74
|
+
delivery masking before the model reads it. See
|
|
75
|
+
`apps/cortex-cc/docs/interactive-tools.md` for authoring them.
|
|
76
|
+
|
|
67
77
|
## Requirements
|
|
68
78
|
|
|
69
79
|
- Bun >= 1.0.0
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The embed ↔ widget handshake for interactive tools.
|
|
3
|
+
*
|
|
4
|
+
* A page embedded by the chat widget (a hosted checkout, signing page, …)
|
|
5
|
+
* reports its outcome with:
|
|
6
|
+
*
|
|
7
|
+
* window.parent.postMessage(
|
|
8
|
+
* { type: "cortex:interactive", status: "completed", reference: "session_123" },
|
|
9
|
+
* "*",
|
|
10
|
+
* );
|
|
11
|
+
*
|
|
12
|
+
* The widget accepts the message only when the event's origin matches the
|
|
13
|
+
* tool's configured `embedOrigin`, and treats the payload as untrusted — for
|
|
14
|
+
* tools with a verify endpoint the server re-checks the reference before the
|
|
15
|
+
* agent sees a result.
|
|
16
|
+
*
|
|
17
|
+
* Like `wire.ts`, this file is compiled into the client SDKs and must stay
|
|
18
|
+
* free of runtime dependencies. It is also the spec any future SDK (Flutter)
|
|
19
|
+
* reimplements against.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const INTERACTIVE_MESSAGE_TYPE = "cortex:interactive";
|
|
23
|
+
|
|
24
|
+
export const INTERACTIVE_STATUSES = ["completed", "cancelled", "failed"] as const;
|
|
25
|
+
|
|
26
|
+
export type InteractiveStatus = (typeof INTERACTIVE_STATUSES)[number];
|
|
27
|
+
|
|
28
|
+
export type InteractiveHandshake = {
|
|
29
|
+
type: typeof INTERACTIVE_MESSAGE_TYPE;
|
|
30
|
+
status: InteractiveStatus;
|
|
31
|
+
/** Opaque id the tool's verify endpoint resolves (session id, envelope id, …). */
|
|
32
|
+
reference?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse a `message` event into a handshake, or null when the origin doesn't
|
|
37
|
+
* match the tool's `embedOrigin` or the payload isn't a well-formed handshake.
|
|
38
|
+
* Call from the widget's `message` listener with `event.data` / `event.origin`.
|
|
39
|
+
*/
|
|
40
|
+
export function parseInteractiveHandshake(data: unknown, origin: string, embedOrigin: string) {
|
|
41
|
+
if (origin !== embedOrigin) return null;
|
|
42
|
+
if (typeof data !== "object" || data === null) return null;
|
|
43
|
+
const message = data as Record<string, unknown>;
|
|
44
|
+
if (message["type"] !== INTERACTIVE_MESSAGE_TYPE) return null;
|
|
45
|
+
const status = INTERACTIVE_STATUSES.find((known) => known === message["status"]);
|
|
46
|
+
if (!status) return null;
|
|
47
|
+
const reference = message["reference"];
|
|
48
|
+
return {
|
|
49
|
+
type: INTERACTIVE_MESSAGE_TYPE,
|
|
50
|
+
status,
|
|
51
|
+
...(typeof reference === "string" ? { reference } : {}),
|
|
52
|
+
} satisfies InteractiveHandshake;
|
|
53
|
+
}
|
package/contracts/runtime.ts
CHANGED
|
@@ -89,6 +89,19 @@ export const knowledgeChunkSchema = z.object({
|
|
|
89
89
|
}),
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Runtime contract §5.4: present on signatures of `interactive` tools — flows
|
|
94
|
+
* the end user completes in an embedded surface inside the chat widget. The
|
|
95
|
+
* tool's endpoint fields act as the *initiate* call (returns the embed URL);
|
|
96
|
+
* `hasVerify` marks a second, server-trusted call that settles the result.
|
|
97
|
+
*/
|
|
98
|
+
export const toolInteractionSchema = z.object({
|
|
99
|
+
surface: z.enum(["inline", "modal"]),
|
|
100
|
+
embedOrigin: z.url(),
|
|
101
|
+
hasVerify: z.boolean(),
|
|
102
|
+
resultDelivery: z.enum(["agent", "endpoint", "both"]),
|
|
103
|
+
});
|
|
104
|
+
|
|
92
105
|
export const toolSignatureSchema = z.object({
|
|
93
106
|
toolId: z.uuid(),
|
|
94
107
|
name: z.string().regex(TOOL_NAME_PATTERN),
|
|
@@ -98,6 +111,11 @@ export const toolSignatureSchema = z.object({
|
|
|
98
111
|
signature: z.string(),
|
|
99
112
|
score: z.number().nullable(),
|
|
100
113
|
pinned: z.boolean(),
|
|
114
|
+
interaction: toolInteractionSchema.optional(),
|
|
115
|
+
/** Interactive tools only: the published input JSON Schema, verbatim.
|
|
116
|
+
* Client-executed declarations need a real schema — the rendered
|
|
117
|
+
* `signature` text alone is not enough for function calling. */
|
|
118
|
+
inputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
101
119
|
});
|
|
102
120
|
|
|
103
121
|
export const serviceCardSchema = z.object({
|
|
@@ -194,6 +212,9 @@ export const searchKnowledgeResponseSchema = z.object({
|
|
|
194
212
|
|
|
195
213
|
export const executeRequestSchema = z.object({
|
|
196
214
|
input: z.record(z.string(), z.unknown()).default({}),
|
|
215
|
+
// Interactive tools only: absent | "initiate" targets the tool's endpoint
|
|
216
|
+
// fields, "verify" targets its verify endpoint config.
|
|
217
|
+
phase: z.enum(["initiate", "verify"]).optional(),
|
|
197
218
|
context: z
|
|
198
219
|
.object({
|
|
199
220
|
threadId: z.string().max(128).optional(),
|
|
@@ -236,6 +257,7 @@ export const runtimeErrorSchema = z.object({
|
|
|
236
257
|
}),
|
|
237
258
|
});
|
|
238
259
|
|
|
260
|
+
export type ToolInteraction = z.infer<typeof toolInteractionSchema>;
|
|
239
261
|
export type RuntimeAgentConfig = z.infer<typeof runtimeAgentConfigSchema>;
|
|
240
262
|
export type ResolveRequest = z.input<typeof resolveRequestSchema>;
|
|
241
263
|
export type ResolveResponse = z.infer<typeof resolveResponseSchema>;
|
package/contracts/wire.ts
CHANGED
|
@@ -70,12 +70,29 @@ export type TokenUsage = {
|
|
|
70
70
|
total: number;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* How to launch one interactive tool: stamped by the server, per tool name, on
|
|
75
|
+
* the metadata of an assistant message that may carry its pending call. Kept in
|
|
76
|
+
* the message so the binding survives server restarts while a run is parked.
|
|
77
|
+
*/
|
|
78
|
+
export type InteractiveToolBinding = {
|
|
79
|
+
toolId: string;
|
|
80
|
+
surface: "inline" | "modal";
|
|
81
|
+
embedOrigin: string;
|
|
82
|
+
hasVerify: boolean;
|
|
83
|
+
resultDelivery: "agent" | "endpoint" | "both";
|
|
84
|
+
};
|
|
85
|
+
|
|
73
86
|
export type MessageMetadata = {
|
|
74
87
|
modelId?: string;
|
|
75
88
|
providerMetadata?: unknown;
|
|
76
89
|
isAborted?: boolean;
|
|
77
90
|
tokenUsage?: TokenUsage;
|
|
78
91
|
attachments?: AttachmentSummary[];
|
|
92
|
+
interactiveTools?: Record<string, InteractiveToolBinding>;
|
|
93
|
+
/** Per tool call: the trusted reference its initiate call returned. A
|
|
94
|
+
* verified completion must match it — the browser's word is never enough. */
|
|
95
|
+
interactiveReferences?: Record<string, string>;
|
|
79
96
|
};
|
|
80
97
|
|
|
81
98
|
/**
|
|
@@ -93,6 +110,21 @@ export type CortexMessage<TPart = unknown> = {
|
|
|
93
110
|
metadata?: MessageMetadata;
|
|
94
111
|
};
|
|
95
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Response of `POST /chat/:chatId/tools/:toolCallId/initiate`. `interactive:
|
|
115
|
+
* false` means the pending call is not an interactive CC tool — the widget
|
|
116
|
+
* falls back to its default rendering for the call. The payload is for the
|
|
117
|
+
* widget only; it never reaches the LLM.
|
|
118
|
+
*/
|
|
119
|
+
export type InteractiveInitiateResult =
|
|
120
|
+
| { interactive: false }
|
|
121
|
+
| {
|
|
122
|
+
interactive: true;
|
|
123
|
+
embedUrl: string;
|
|
124
|
+
surface: "inline" | "modal";
|
|
125
|
+
embedOrigin: string;
|
|
126
|
+
};
|
|
127
|
+
|
|
96
128
|
export type ThreadCreatedEvent = {
|
|
97
129
|
type: "thread:created";
|
|
98
130
|
payload: { thread: ThreadSummary };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The embed ↔ widget handshake for interactive tools.
|
|
3
|
+
*
|
|
4
|
+
* A page embedded by the chat widget (a hosted checkout, signing page, …)
|
|
5
|
+
* reports its outcome with:
|
|
6
|
+
*
|
|
7
|
+
* window.parent.postMessage(
|
|
8
|
+
* { type: "cortex:interactive", status: "completed", reference: "session_123" },
|
|
9
|
+
* "*",
|
|
10
|
+
* );
|
|
11
|
+
*
|
|
12
|
+
* The widget accepts the message only when the event's origin matches the
|
|
13
|
+
* tool's configured `embedOrigin`, and treats the payload as untrusted — for
|
|
14
|
+
* tools with a verify endpoint the server re-checks the reference before the
|
|
15
|
+
* agent sees a result.
|
|
16
|
+
*
|
|
17
|
+
* Like `wire.ts`, this file is compiled into the client SDKs and must stay
|
|
18
|
+
* free of runtime dependencies. It is also the spec any future SDK (Flutter)
|
|
19
|
+
* reimplements against.
|
|
20
|
+
*/
|
|
21
|
+
export declare const INTERACTIVE_MESSAGE_TYPE = "cortex:interactive";
|
|
22
|
+
export declare const INTERACTIVE_STATUSES: readonly ["completed", "cancelled", "failed"];
|
|
23
|
+
export type InteractiveStatus = (typeof INTERACTIVE_STATUSES)[number];
|
|
24
|
+
export type InteractiveHandshake = {
|
|
25
|
+
type: typeof INTERACTIVE_MESSAGE_TYPE;
|
|
26
|
+
status: InteractiveStatus;
|
|
27
|
+
/** Opaque id the tool's verify endpoint resolves (session id, envelope id, …). */
|
|
28
|
+
reference?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Parse a `message` event into a handshake, or null when the origin doesn't
|
|
32
|
+
* match the tool's `embedOrigin` or the payload isn't a well-formed handshake.
|
|
33
|
+
* Call from the widget's `message` listener with `event.data` / `event.origin`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseInteractiveHandshake(data: unknown, origin: string, embedOrigin: string): {
|
|
36
|
+
reference?: string | undefined;
|
|
37
|
+
type: "cortex:interactive";
|
|
38
|
+
status: "failed" | "completed" | "cancelled";
|
|
39
|
+
} | null;
|
|
@@ -60,6 +60,25 @@ export declare const knowledgeChunkSchema: z.ZodObject<{
|
|
|
60
60
|
service: z.ZodNullable<z.ZodString>;
|
|
61
61
|
}, z.core.$strip>;
|
|
62
62
|
}, z.core.$strip>;
|
|
63
|
+
/**
|
|
64
|
+
* Runtime contract §5.4: present on signatures of `interactive` tools — flows
|
|
65
|
+
* the end user completes in an embedded surface inside the chat widget. The
|
|
66
|
+
* tool's endpoint fields act as the *initiate* call (returns the embed URL);
|
|
67
|
+
* `hasVerify` marks a second, server-trusted call that settles the result.
|
|
68
|
+
*/
|
|
69
|
+
export declare const toolInteractionSchema: z.ZodObject<{
|
|
70
|
+
surface: z.ZodEnum<{
|
|
71
|
+
inline: "inline";
|
|
72
|
+
modal: "modal";
|
|
73
|
+
}>;
|
|
74
|
+
embedOrigin: z.ZodURL;
|
|
75
|
+
hasVerify: z.ZodBoolean;
|
|
76
|
+
resultDelivery: z.ZodEnum<{
|
|
77
|
+
agent: "agent";
|
|
78
|
+
endpoint: "endpoint";
|
|
79
|
+
both: "both";
|
|
80
|
+
}>;
|
|
81
|
+
}, z.core.$strip>;
|
|
63
82
|
export declare const toolSignatureSchema: z.ZodObject<{
|
|
64
83
|
toolId: z.ZodUUID;
|
|
65
84
|
name: z.ZodString;
|
|
@@ -69,6 +88,20 @@ export declare const toolSignatureSchema: z.ZodObject<{
|
|
|
69
88
|
signature: z.ZodString;
|
|
70
89
|
score: z.ZodNullable<z.ZodNumber>;
|
|
71
90
|
pinned: z.ZodBoolean;
|
|
91
|
+
interaction: z.ZodOptional<z.ZodObject<{
|
|
92
|
+
surface: z.ZodEnum<{
|
|
93
|
+
inline: "inline";
|
|
94
|
+
modal: "modal";
|
|
95
|
+
}>;
|
|
96
|
+
embedOrigin: z.ZodURL;
|
|
97
|
+
hasVerify: z.ZodBoolean;
|
|
98
|
+
resultDelivery: z.ZodEnum<{
|
|
99
|
+
agent: "agent";
|
|
100
|
+
endpoint: "endpoint";
|
|
101
|
+
both: "both";
|
|
102
|
+
}>;
|
|
103
|
+
}, z.core.$strip>>;
|
|
104
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
72
105
|
}, z.core.$strip>;
|
|
73
106
|
export declare const serviceCardSchema: z.ZodObject<{
|
|
74
107
|
serviceId: z.ZodUUID;
|
|
@@ -152,6 +185,20 @@ export declare const resolveResponseSchema: z.ZodObject<{
|
|
|
152
185
|
signature: z.ZodString;
|
|
153
186
|
score: z.ZodNullable<z.ZodNumber>;
|
|
154
187
|
pinned: z.ZodBoolean;
|
|
188
|
+
interaction: z.ZodOptional<z.ZodObject<{
|
|
189
|
+
surface: z.ZodEnum<{
|
|
190
|
+
inline: "inline";
|
|
191
|
+
modal: "modal";
|
|
192
|
+
}>;
|
|
193
|
+
embedOrigin: z.ZodURL;
|
|
194
|
+
hasVerify: z.ZodBoolean;
|
|
195
|
+
resultDelivery: z.ZodEnum<{
|
|
196
|
+
agent: "agent";
|
|
197
|
+
endpoint: "endpoint";
|
|
198
|
+
both: "both";
|
|
199
|
+
}>;
|
|
200
|
+
}, z.core.$strip>>;
|
|
201
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
155
202
|
}, z.core.$strip>>;
|
|
156
203
|
services: z.ZodArray<z.ZodObject<{
|
|
157
204
|
serviceId: z.ZodUUID;
|
|
@@ -191,6 +238,20 @@ export declare const searchToolsResponseSchema: z.ZodObject<{
|
|
|
191
238
|
signature: z.ZodString;
|
|
192
239
|
score: z.ZodNullable<z.ZodNumber>;
|
|
193
240
|
pinned: z.ZodBoolean;
|
|
241
|
+
interaction: z.ZodOptional<z.ZodObject<{
|
|
242
|
+
surface: z.ZodEnum<{
|
|
243
|
+
inline: "inline";
|
|
244
|
+
modal: "modal";
|
|
245
|
+
}>;
|
|
246
|
+
embedOrigin: z.ZodURL;
|
|
247
|
+
hasVerify: z.ZodBoolean;
|
|
248
|
+
resultDelivery: z.ZodEnum<{
|
|
249
|
+
agent: "agent";
|
|
250
|
+
endpoint: "endpoint";
|
|
251
|
+
both: "both";
|
|
252
|
+
}>;
|
|
253
|
+
}, z.core.$strip>>;
|
|
254
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
194
255
|
}, z.core.$strip>>;
|
|
195
256
|
sharedShapes: z.ZodArray<z.ZodObject<{
|
|
196
257
|
ref: z.ZodString;
|
|
@@ -215,6 +276,20 @@ export declare const searchServicesResponseSchema: z.ZodObject<{
|
|
|
215
276
|
signature: z.ZodString;
|
|
216
277
|
score: z.ZodNullable<z.ZodNumber>;
|
|
217
278
|
pinned: z.ZodBoolean;
|
|
279
|
+
interaction: z.ZodOptional<z.ZodObject<{
|
|
280
|
+
surface: z.ZodEnum<{
|
|
281
|
+
inline: "inline";
|
|
282
|
+
modal: "modal";
|
|
283
|
+
}>;
|
|
284
|
+
embedOrigin: z.ZodURL;
|
|
285
|
+
hasVerify: z.ZodBoolean;
|
|
286
|
+
resultDelivery: z.ZodEnum<{
|
|
287
|
+
agent: "agent";
|
|
288
|
+
endpoint: "endpoint";
|
|
289
|
+
both: "both";
|
|
290
|
+
}>;
|
|
291
|
+
}, z.core.$strip>>;
|
|
292
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
218
293
|
}, z.core.$strip>>;
|
|
219
294
|
sharedShapes: z.ZodArray<z.ZodObject<{
|
|
220
295
|
ref: z.ZodString;
|
|
@@ -239,6 +314,10 @@ export declare const searchKnowledgeResponseSchema: z.ZodObject<{
|
|
|
239
314
|
}, z.core.$strip>;
|
|
240
315
|
export declare const executeRequestSchema: z.ZodObject<{
|
|
241
316
|
input: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
317
|
+
phase: z.ZodOptional<z.ZodEnum<{
|
|
318
|
+
verify: "verify";
|
|
319
|
+
initiate: "initiate";
|
|
320
|
+
}>>;
|
|
242
321
|
context: z.ZodOptional<z.ZodObject<{
|
|
243
322
|
threadId: z.ZodOptional<z.ZodString>;
|
|
244
323
|
userId: z.ZodOptional<z.ZodString>;
|
|
@@ -302,6 +381,7 @@ export declare const runtimeErrorSchema: z.ZodObject<{
|
|
|
302
381
|
upstreamStatus: z.ZodOptional<z.ZodNumber>;
|
|
303
382
|
}, z.core.$strip>;
|
|
304
383
|
}, z.core.$strip>;
|
|
384
|
+
export type ToolInteraction = z.infer<typeof toolInteractionSchema>;
|
|
305
385
|
export type RuntimeAgentConfig = z.infer<typeof runtimeAgentConfigSchema>;
|
|
306
386
|
export type ResolveRequest = z.input<typeof resolveRequestSchema>;
|
|
307
387
|
export type ResolveResponse = z.infer<typeof resolveResponseSchema>;
|
package/dist/contracts/wire.d.ts
CHANGED
|
@@ -50,12 +50,28 @@ export type TokenUsage = {
|
|
|
50
50
|
};
|
|
51
51
|
total: number;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* How to launch one interactive tool: stamped by the server, per tool name, on
|
|
55
|
+
* the metadata of an assistant message that may carry its pending call. Kept in
|
|
56
|
+
* the message so the binding survives server restarts while a run is parked.
|
|
57
|
+
*/
|
|
58
|
+
export type InteractiveToolBinding = {
|
|
59
|
+
toolId: string;
|
|
60
|
+
surface: "inline" | "modal";
|
|
61
|
+
embedOrigin: string;
|
|
62
|
+
hasVerify: boolean;
|
|
63
|
+
resultDelivery: "agent" | "endpoint" | "both";
|
|
64
|
+
};
|
|
53
65
|
export type MessageMetadata = {
|
|
54
66
|
modelId?: string;
|
|
55
67
|
providerMetadata?: unknown;
|
|
56
68
|
isAborted?: boolean;
|
|
57
69
|
tokenUsage?: TokenUsage;
|
|
58
70
|
attachments?: AttachmentSummary[];
|
|
71
|
+
interactiveTools?: Record<string, InteractiveToolBinding>;
|
|
72
|
+
/** Per tool call: the trusted reference its initiate call returned. A
|
|
73
|
+
* verified completion must match it — the browser's word is never enough. */
|
|
74
|
+
interactiveReferences?: Record<string, string>;
|
|
59
75
|
};
|
|
60
76
|
/**
|
|
61
77
|
* A stored message as it is persisted and served. TanStack AI's `UIMessage`
|
|
@@ -71,6 +87,20 @@ export type CortexMessage<TPart = unknown> = {
|
|
|
71
87
|
parts: TPart[];
|
|
72
88
|
metadata?: MessageMetadata;
|
|
73
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Response of `POST /chat/:chatId/tools/:toolCallId/initiate`. `interactive:
|
|
92
|
+
* false` means the pending call is not an interactive CC tool — the widget
|
|
93
|
+
* falls back to its default rendering for the call. The payload is for the
|
|
94
|
+
* widget only; it never reaches the LLM.
|
|
95
|
+
*/
|
|
96
|
+
export type InteractiveInitiateResult = {
|
|
97
|
+
interactive: false;
|
|
98
|
+
} | {
|
|
99
|
+
interactive: true;
|
|
100
|
+
embedUrl: string;
|
|
101
|
+
surface: "inline" | "modal";
|
|
102
|
+
embedOrigin: string;
|
|
103
|
+
};
|
|
74
104
|
export type ThreadCreatedEvent = {
|
|
75
105
|
type: "thread:created";
|
|
76
106
|
payload: {
|
|
@@ -34,6 +34,13 @@ export type DatabaseAdapter = {
|
|
|
34
34
|
upsert(threadId: string, messages: ChatMessage[], options?: {
|
|
35
35
|
replaceAttachments?: boolean;
|
|
36
36
|
}): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Atomically merges one anchored interactive reference into the
|
|
39
|
+
* message's metadata as a single-statement JSON merge — safe under
|
|
40
|
+
* concurrent writers across replicas, unlike a read-modify-write of
|
|
41
|
+
* the whole message.
|
|
42
|
+
*/
|
|
43
|
+
mergeInteractiveReference(threadId: string, messageId: string, toolCallId: string, reference: string): Promise<void>;
|
|
37
44
|
};
|
|
38
45
|
llmRequests: {
|
|
39
46
|
insert(requests: {
|
|
@@ -16,6 +16,8 @@ export declare function withOwnedAttachments(messages: ChatMessage[], replaceAtt
|
|
|
16
16
|
providerMetadata?: unknown;
|
|
17
17
|
isAborted?: boolean;
|
|
18
18
|
tokenUsage?: import("../../types").TokenUsage;
|
|
19
|
+
interactiveTools?: Record<string, import("../..").InteractiveToolBinding>;
|
|
20
|
+
interactiveReferences?: Record<string, string>;
|
|
19
21
|
};
|
|
20
22
|
id: string;
|
|
21
23
|
role: "system" | "user" | "assistant";
|
|
@@ -76,6 +76,7 @@ export declare function createMssqlAdapter(connectionString: string, storage?: S
|
|
|
76
76
|
upsert(threadId: string, messagesToUpsert: import("../../../types").ChatMessage[], options?: {
|
|
77
77
|
replaceAttachments?: boolean;
|
|
78
78
|
}): Promise<void>;
|
|
79
|
+
mergeInteractiveReference(threadId: string, messageId: string, toolCallId: string, reference: string): Promise<void>;
|
|
79
80
|
};
|
|
80
81
|
llmRequests: {
|
|
81
82
|
insert(requests: {
|
|
@@ -34,4 +34,5 @@ export declare function createMessagesRepository(db: MssqlDb): {
|
|
|
34
34
|
upsert(threadId: string, messagesToUpsert: ChatMessage[], options?: {
|
|
35
35
|
replaceAttachments?: boolean;
|
|
36
36
|
}): Promise<void>;
|
|
37
|
+
mergeInteractiveReference(threadId: string, messageId: string, toolCallId: string, reference: string): Promise<void>;
|
|
37
38
|
};
|
|
@@ -76,6 +76,7 @@ export declare function createPostgresAdapter(connectionString: string, storage?
|
|
|
76
76
|
upsert(threadId: string, messagesToUpsert: import("../../../types").ChatMessage[], options?: {
|
|
77
77
|
replaceAttachments?: boolean;
|
|
78
78
|
}): Promise<void>;
|
|
79
|
+
mergeInteractiveReference(threadId: string, messageId: string, toolCallId: string, reference: string): Promise<void>;
|
|
79
80
|
};
|
|
80
81
|
llmRequests: {
|
|
81
82
|
insert(requests: {
|
|
@@ -34,4 +34,5 @@ export declare function createMessagesRepository(db: PostgresDb): {
|
|
|
34
34
|
upsert(threadId: string, messagesToUpsert: ChatMessage[], options?: {
|
|
35
35
|
replaceAttachments?: boolean;
|
|
36
36
|
}): Promise<void>;
|
|
37
|
+
mergeInteractiveReference(threadId: string, messageId: string, toolCallId: string, reference: string): Promise<void>;
|
|
37
38
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ResolvedCortexAgentConfig } from "../config";
|
|
2
2
|
import type { Thread } from "../types";
|
|
3
3
|
import type { ControlCenterClient } from "../cc/client";
|
|
4
|
-
import type { CcToolRegistry } from "../cc/registry";
|
|
4
|
+
import type { CcInteractiveTool, CcToolRegistry } from "../cc/registry";
|
|
5
5
|
import type { ResolveResponse, RuntimeAgentConfig } from "../cc/types";
|
|
6
6
|
type CcRuntimeOptions = {
|
|
7
7
|
ccClient: ControlCenterClient | null;
|
|
@@ -48,6 +48,7 @@ export declare function createCcRuntime(options: CcRuntimeOptions): {
|
|
|
48
48
|
publishedAt: string;
|
|
49
49
|
};
|
|
50
50
|
registry: CcToolRegistry;
|
|
51
|
+
interactiveTools: Map<string, CcInteractiveTool>;
|
|
51
52
|
threadId: string;
|
|
52
53
|
turnKey: string;
|
|
53
54
|
userId: string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ResolvedCortexAgentConfig } from "../config";
|
|
2
|
+
import type { ChatMessage, Thread } from "../types";
|
|
3
|
+
import { ControlCenterClient } from "../cc/client";
|
|
4
|
+
import type { CcRuntime } from "../cc/registry";
|
|
5
|
+
/**
|
|
6
|
+
* The wire-shape bindings stamped on the turn's final assistant message, so a
|
|
7
|
+
* parked interactive call can be initiated and verified after any restart.
|
|
8
|
+
* Only names that actually won declaration merging are stamped — a call to a
|
|
9
|
+
* colliding consumer-owned tool must never be treated as interactive. Undefined
|
|
10
|
+
* when nothing qualifies, keeping metadata lean.
|
|
11
|
+
*/
|
|
12
|
+
export declare function interactiveToolBindings(cc: CcRuntime | undefined, takenNames: ReadonlySet<string>): {
|
|
13
|
+
[k: string]: {
|
|
14
|
+
surface: "inline" | "modal";
|
|
15
|
+
embedOrigin: string;
|
|
16
|
+
hasVerify: boolean;
|
|
17
|
+
resultDelivery: "agent" | "endpoint" | "both";
|
|
18
|
+
toolId: string;
|
|
19
|
+
};
|
|
20
|
+
} | undefined;
|
|
21
|
+
/** Null when the agent has no Control Center — every caller treats that as "skip". */
|
|
22
|
+
export declare function createCcClient(config: ResolvedCortexAgentConfig): ControlCenterClient | null;
|
|
23
|
+
/**
|
|
24
|
+
* `POST /chat/:chatId/tools/:toolCallId/initiate` — runs the interactive
|
|
25
|
+
* tool's initiate call (the tool's own endpoint) and hands the widget its
|
|
26
|
+
* embed payload. The model never sees this payload. The idempotency key is
|
|
27
|
+
* pinned to the tool call, so a reload mid-flow reuses the created session
|
|
28
|
+
* instead of opening a second one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function initiateInteractiveTool(options: {
|
|
31
|
+
config: ResolvedCortexAgentConfig;
|
|
32
|
+
thread: Thread;
|
|
33
|
+
userId: string;
|
|
34
|
+
token: string;
|
|
35
|
+
toolCallId: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
interactive: false;
|
|
38
|
+
embedUrl?: undefined;
|
|
39
|
+
surface?: undefined;
|
|
40
|
+
embedOrigin?: undefined;
|
|
41
|
+
} | {
|
|
42
|
+
interactive: true;
|
|
43
|
+
embedUrl: string;
|
|
44
|
+
surface: "inline" | "modal";
|
|
45
|
+
embedOrigin: string;
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* The trust boundary for interactive results: whatever the client submitted
|
|
49
|
+
* for a bound tool call is replaced wholesale — cancelled/failed pass through
|
|
50
|
+
* normalized, completed is re-established by the tool's verify call when it
|
|
51
|
+
* has one, and result delivery masking is applied before the model reads it.
|
|
52
|
+
* Runs before the answering turn is persisted, so both the stored transcript
|
|
53
|
+
* and the model context carry only settled results.
|
|
54
|
+
*/
|
|
55
|
+
export declare function settleInteractiveToolResults(options: {
|
|
56
|
+
messages: ChatMessage[];
|
|
57
|
+
thread: Thread;
|
|
58
|
+
userId: string;
|
|
59
|
+
token: string;
|
|
60
|
+
config: ResolvedCortexAgentConfig;
|
|
61
|
+
ccClient: ControlCenterClient | null;
|
|
62
|
+
}): Promise<void>;
|
|
@@ -35,5 +35,19 @@ export declare function createToolInstrumentation(options: TurnToolsOptions & {
|
|
|
35
35
|
onBeforeToolCall(_ctx: import("@tanstack/ai").ChatMiddlewareContext<unknown>, { toolName, toolCallId, args }: import("@tanstack/ai").ToolCallHookContext): void;
|
|
36
36
|
onAfterToolCall(_ctx: import("@tanstack/ai").ChatMiddlewareContext<unknown>, { toolName, toolCallId }: import("@tanstack/ai").AfterToolCallInfo): void;
|
|
37
37
|
} | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Interactive CC tools are declared like the request's client tools: no
|
|
40
|
+
* executor, so the call streams to the widget as a tool-call part and the run
|
|
41
|
+
* parks until the widget answers. The declaration carries the tool's published
|
|
42
|
+
* input schema — function-calling models ignore schemas described only in
|
|
43
|
+
* prose — with a permissive object as the fallback for older Control Centers.
|
|
44
|
+
* Names the consumer already claimed are skipped: those calls belong to the
|
|
45
|
+
* consumer's tool, and must neither be declared nor stamped as interactive.
|
|
46
|
+
*/
|
|
47
|
+
export declare function interactiveToolDeclarations(cc: CcRuntime | undefined, takenNames: ReadonlySet<string>): {
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
parameters: Record<string, unknown>;
|
|
51
|
+
}[];
|
|
38
52
|
export declare function hasDefaultAttachmentInterceptor(config: ResolvedCortexAgentConfig): boolean;
|
|
39
53
|
export {};
|
|
@@ -8,6 +8,9 @@ export type ExecuteOptions = {
|
|
|
8
8
|
turnKey: string;
|
|
9
9
|
stepIndex: number;
|
|
10
10
|
callIndex: number;
|
|
11
|
+
/** Overrides the composed key — used where stability must outlive the turn
|
|
12
|
+
* counters, e.g. one interactive initiate per tool call across reloads. */
|
|
13
|
+
idempotencyKey?: string;
|
|
11
14
|
timeoutMs?: number;
|
|
12
15
|
abortSignal?: AbortSignal;
|
|
13
16
|
};
|
|
@@ -74,6 +77,13 @@ export declare class ControlCenterClient {
|
|
|
74
77
|
signature: string;
|
|
75
78
|
score: number | null;
|
|
76
79
|
pinned: boolean;
|
|
80
|
+
interaction?: {
|
|
81
|
+
surface: "inline" | "modal";
|
|
82
|
+
embedOrigin: string;
|
|
83
|
+
hasVerify: boolean;
|
|
84
|
+
resultDelivery: "agent" | "endpoint" | "both";
|
|
85
|
+
} | undefined;
|
|
86
|
+
inputSchema?: Record<string, unknown> | undefined;
|
|
77
87
|
}[];
|
|
78
88
|
services: {
|
|
79
89
|
serviceId: string;
|
|
@@ -103,6 +113,13 @@ export declare class ControlCenterClient {
|
|
|
103
113
|
signature: string;
|
|
104
114
|
score: number | null;
|
|
105
115
|
pinned: boolean;
|
|
116
|
+
interaction?: {
|
|
117
|
+
surface: "inline" | "modal";
|
|
118
|
+
embedOrigin: string;
|
|
119
|
+
hasVerify: boolean;
|
|
120
|
+
resultDelivery: "agent" | "endpoint" | "both";
|
|
121
|
+
} | undefined;
|
|
122
|
+
inputSchema?: Record<string, unknown> | undefined;
|
|
106
123
|
}[];
|
|
107
124
|
sharedShapes: {
|
|
108
125
|
ref: string;
|
|
@@ -127,6 +144,13 @@ export declare class ControlCenterClient {
|
|
|
127
144
|
signature: string;
|
|
128
145
|
score: number | null;
|
|
129
146
|
pinned: boolean;
|
|
147
|
+
interaction?: {
|
|
148
|
+
surface: "inline" | "modal";
|
|
149
|
+
embedOrigin: string;
|
|
150
|
+
hasVerify: boolean;
|
|
151
|
+
resultDelivery: "agent" | "endpoint" | "both";
|
|
152
|
+
} | undefined;
|
|
153
|
+
inputSchema?: Record<string, unknown> | undefined;
|
|
130
154
|
}[];
|
|
131
155
|
sharedShapes: {
|
|
132
156
|
ref: string;
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import type { ControlCenterClient } from "./client";
|
|
2
|
-
import type { RuntimeAgentConfig } from "./types";
|
|
2
|
+
import type { RuntimeAgentConfig, ToolInteraction } from "./types";
|
|
3
3
|
export type CcToolBinding = {
|
|
4
4
|
toolId: string;
|
|
5
5
|
readOnly: boolean;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* An interactive tool resolved for this turn. Declared to the model as a real
|
|
9
|
+
* client-executed tool — never a sandbox binding — so its call streams to the
|
|
10
|
+
* widget as a tool-call part and parks the run until the flow settles.
|
|
11
|
+
*/
|
|
12
|
+
export type CcInteractiveTool = {
|
|
13
|
+
toolId: string;
|
|
14
|
+
signature: string;
|
|
15
|
+
interaction: ToolInteraction;
|
|
16
|
+
inputSchema?: Record<string, unknown>;
|
|
17
|
+
};
|
|
7
18
|
/**
|
|
8
19
|
* Turn-scoped and mutable: seeded from /resolve, extended by searchTools
|
|
9
20
|
* mid-turn so a tool discovered at step 3 is callable at step 4.
|
|
@@ -13,6 +24,7 @@ export declare function registerCcTools(registry: CcToolRegistry, tools: {
|
|
|
13
24
|
name: string;
|
|
14
25
|
toolId: string;
|
|
15
26
|
readOnly: boolean;
|
|
27
|
+
interaction?: ToolInteraction;
|
|
16
28
|
}[]): void;
|
|
17
29
|
export declare function recordRecentCcTool(threadId: string, name: string): void;
|
|
18
30
|
export declare function getRecentCcTools(threadId: string): string[];
|
|
@@ -22,6 +34,7 @@ export type CcRuntime = {
|
|
|
22
34
|
agentId: string;
|
|
23
35
|
config: RuntimeAgentConfig;
|
|
24
36
|
registry: CcToolRegistry;
|
|
37
|
+
interactiveTools: Map<string, CcInteractiveTool>;
|
|
25
38
|
threadId: string;
|
|
26
39
|
turnKey: string;
|
|
27
40
|
userId: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import z from "zod";
|
|
2
|
-
export { AGENT_SLUG_PATTERN, executeResponseSchema, resolveResponseSchema, runtimeAgentConfigSchema, searchKnowledgeResponseSchema, searchServicesResponseSchema, searchToolsResponseSchema, type ExecuteRequest, type ResolveRequest, type ResolveResponse, type RuntimeAgentConfig, type SearchRequest, } from "../../../contracts/runtime";
|
|
2
|
+
export { AGENT_SLUG_PATTERN, executeResponseSchema, resolveResponseSchema, runtimeAgentConfigSchema, searchKnowledgeResponseSchema, searchServicesResponseSchema, searchToolsResponseSchema, type ExecuteRequest, type ResolveRequest, type ResolveResponse, type RuntimeAgentConfig, type SearchRequest, type ToolInteraction, } from "../../../contracts/runtime";
|
|
3
3
|
/**
|
|
4
4
|
* The contract's error envelope, hardened for this consumer: a newer runtime may
|
|
5
5
|
* answer with an error kind this build has never heard of, which must degrade to a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@m6d/cortex-server",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Reusable AI agent chat server library for Hono + Bun",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -74,5 +74,8 @@
|
|
|
74
74
|
},
|
|
75
75
|
"publishConfig": {
|
|
76
76
|
"access": "public"
|
|
77
|
-
}
|
|
77
|
+
},
|
|
78
|
+
"releaseWatchPaths": [
|
|
79
|
+
"internal/contracts"
|
|
80
|
+
]
|
|
78
81
|
}
|