@m6d/cortex-server 2.0.0 → 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.
Files changed (37) hide show
  1. package/README.md +10 -0
  2. package/contracts/interactive.ts +53 -0
  3. package/contracts/rich-text.ts +207 -0
  4. package/contracts/runtime.ts +58 -1
  5. package/contracts/wire.ts +32 -0
  6. package/dist/contracts/interactive.d.ts +39 -0
  7. package/dist/contracts/rich-text.d.ts +32 -0
  8. package/dist/contracts/runtime.d.ts +119 -7
  9. package/dist/contracts/wire.d.ts +30 -0
  10. package/dist/src/lib/adapters/database/index.d.ts +7 -0
  11. package/dist/src/lib/adapters/database/message-content.d.ts +2 -0
  12. package/dist/src/lib/adapters/database/mssql/index.d.ts +1 -0
  13. package/dist/src/lib/adapters/database/mssql/messages.d.ts +1 -0
  14. package/dist/src/lib/adapters/database/postgres/index.d.ts +1 -0
  15. package/dist/src/lib/adapters/database/postgres/messages.d.ts +1 -0
  16. package/dist/src/lib/ai/cc-runtime.d.ts +5 -4
  17. package/dist/src/lib/ai/interactive.d.ts +62 -0
  18. package/dist/src/lib/ai/tools/search-common.d.ts +1 -1
  19. package/dist/src/lib/ai/turn-tools.d.ts +14 -0
  20. package/dist/src/lib/cc/client.d.ts +27 -3
  21. package/dist/src/lib/cc/config-cache.d.ts +2 -2
  22. package/dist/src/lib/cc/registry.d.ts +14 -1
  23. package/dist/src/lib/cc/types.d.ts +1 -1
  24. package/package.json +5 -2
  25. package/src/lib/adapters/database/index.ts +12 -0
  26. package/src/lib/adapters/database/mssql/messages.ts +28 -1
  27. package/src/lib/adapters/database/postgres/messages.ts +22 -1
  28. package/src/lib/ai/cc-runtime.ts +16 -1
  29. package/src/lib/ai/index.ts +41 -5
  30. package/src/lib/ai/interactive.ts +364 -0
  31. package/src/lib/ai/tools/search-tools.tool.ts +6 -2
  32. package/src/lib/ai/turn-tools.ts +23 -0
  33. package/src/lib/cc/client.ts +5 -1
  34. package/src/lib/cc/format.ts +8 -9
  35. package/src/lib/cc/registry.ts +16 -2
  36. package/src/lib/cc/types.ts +1 -0
  37. package/src/lib/routes/chat.ts +23 -0
@@ -3,14 +3,44 @@ import { z } from "zod";
3
3
  export declare const AGENT_SLUG_PATTERN: RegExp;
4
4
  /** Runtime contract §5: tool names become `tools.<name>()` sandbox bindings. */
5
5
  export declare const TOOL_NAME_PATTERN: RegExp;
6
- export declare const PROMPT_VARIABLES: readonly ["userName", "channel", "locale"];
6
+ export declare const PROMPT_VARIABLES: readonly ["userName", "channel", "locale", "utcTime", "timezone"];
7
+ /**
8
+ * One entry per prompt variable, driving editor autocomplete, validation, and
9
+ * runtime substitution alike. Adding a variable = extending the tuple above
10
+ * plus one entry here (the compiler enforces the pair); the consumer embedding
11
+ * the agent supplies the value in its session/request context under the same
12
+ * key. Descriptions are plain English on purpose: variables are code-like
13
+ * identifiers shown in an LTR suggestion list, not localized UI copy.
14
+ */
15
+ export declare const PROMPT_VARIABLE_DEFINITIONS: {
16
+ userName: {
17
+ description: string;
18
+ example: string;
19
+ };
20
+ channel: {
21
+ description: string;
22
+ example: string;
23
+ };
24
+ locale: {
25
+ description: string;
26
+ example: string;
27
+ };
28
+ utcTime: {
29
+ description: string;
30
+ example: string;
31
+ };
32
+ timezone: {
33
+ description: string;
34
+ example: string;
35
+ };
36
+ };
7
37
  export declare const RUNTIME_ERROR_KINDS: readonly ["timeout", "upstream_4xx", "upstream_5xx", "schema_mismatch", "rate_limited", "not_found", "unauthorized", "invalid_request", "egress_blocked", "internal"];
8
38
  /** The ways an execute output may disagree with the tool's declared schema. */
9
39
  export declare const JSON_SCHEMA_ISSUE_KINDS: readonly ["missing", "unexpected", "type_mismatch"];
10
40
  export declare const agentSlugSchema: z.ZodString;
11
41
  export declare const localeSchema: z.ZodEnum<{
12
- en: "en";
13
42
  ar: "ar";
43
+ en: "en";
14
44
  }>;
15
45
  export declare const catalogVersionSchema: z.ZodString;
16
46
  export declare const sharedShapeSchema: z.ZodObject<{
@@ -30,6 +60,25 @@ export declare const knowledgeChunkSchema: z.ZodObject<{
30
60
  service: z.ZodNullable<z.ZodString>;
31
61
  }, z.core.$strip>;
32
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>;
33
82
  export declare const toolSignatureSchema: z.ZodObject<{
34
83
  toolId: z.ZodUUID;
35
84
  name: z.ZodString;
@@ -39,6 +88,20 @@ export declare const toolSignatureSchema: z.ZodObject<{
39
88
  signature: z.ZodString;
40
89
  score: z.ZodNullable<z.ZodNumber>;
41
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>>;
42
105
  }, z.core.$strip>;
43
106
  export declare const serviceCardSchema: z.ZodObject<{
44
107
  serviceId: z.ZodUUID;
@@ -54,11 +117,13 @@ export declare const runtimeAgentConfigSchema: z.ZodObject<{
54
117
  userName: "userName";
55
118
  channel: "channel";
56
119
  locale: "locale";
120
+ utcTime: "utcTime";
121
+ timezone: "timezone";
57
122
  }>>;
58
123
  catalogBlurb: z.ZodString;
59
124
  defaultLocale: z.ZodEnum<{
60
- en: "en";
61
125
  ar: "ar";
126
+ en: "en";
62
127
  }>;
63
128
  metaTools: z.ZodObject<{
64
129
  searchKnowledge: z.ZodBoolean;
@@ -76,8 +141,8 @@ export declare const runtimeAgentConfigSchema: z.ZodObject<{
76
141
  export declare const resolveRequestSchema: z.ZodObject<{
77
142
  query: z.ZodString;
78
143
  locale: z.ZodOptional<z.ZodEnum<{
79
- en: "en";
80
144
  ar: "ar";
145
+ en: "en";
81
146
  }>>;
82
147
  hints: z.ZodOptional<z.ZodObject<{
83
148
  threadId: z.ZodOptional<z.ZodString>;
@@ -95,8 +160,8 @@ export declare const resolveResponseSchema: z.ZodObject<{
95
160
  resolveId: z.ZodUUID;
96
161
  catalogVersion: z.ZodString;
97
162
  locale: z.ZodEnum<{
98
- en: "en";
99
163
  ar: "ar";
164
+ en: "en";
100
165
  }>;
101
166
  knowledge: z.ZodArray<z.ZodObject<{
102
167
  chunkId: z.ZodUUID;
@@ -120,6 +185,20 @@ export declare const resolveResponseSchema: z.ZodObject<{
120
185
  signature: z.ZodString;
121
186
  score: z.ZodNullable<z.ZodNumber>;
122
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>>;
123
202
  }, z.core.$strip>>;
124
203
  services: z.ZodArray<z.ZodObject<{
125
204
  serviceId: z.ZodUUID;
@@ -142,8 +221,8 @@ export declare const searchRequestSchema: z.ZodObject<{
142
221
  query: z.ZodString;
143
222
  limit: z.ZodDefault<z.ZodNumber>;
144
223
  locale: z.ZodOptional<z.ZodEnum<{
145
- en: "en";
146
224
  ar: "ar";
225
+ en: "en";
147
226
  }>>;
148
227
  threadId: z.ZodOptional<z.ZodString>;
149
228
  service: z.ZodOptional<z.ZodString>;
@@ -159,6 +238,20 @@ export declare const searchToolsResponseSchema: z.ZodObject<{
159
238
  signature: z.ZodString;
160
239
  score: z.ZodNullable<z.ZodNumber>;
161
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>>;
162
255
  }, z.core.$strip>>;
163
256
  sharedShapes: z.ZodArray<z.ZodObject<{
164
257
  ref: z.ZodString;
@@ -183,6 +276,20 @@ export declare const searchServicesResponseSchema: z.ZodObject<{
183
276
  signature: z.ZodString;
184
277
  score: z.ZodNullable<z.ZodNumber>;
185
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>>;
186
293
  }, z.core.$strip>>;
187
294
  sharedShapes: z.ZodArray<z.ZodObject<{
188
295
  ref: z.ZodString;
@@ -207,12 +314,16 @@ export declare const searchKnowledgeResponseSchema: z.ZodObject<{
207
314
  }, z.core.$strip>;
208
315
  export declare const executeRequestSchema: z.ZodObject<{
209
316
  input: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
317
+ phase: z.ZodOptional<z.ZodEnum<{
318
+ verify: "verify";
319
+ initiate: "initiate";
320
+ }>>;
210
321
  context: z.ZodOptional<z.ZodObject<{
211
322
  threadId: z.ZodOptional<z.ZodString>;
212
323
  userId: z.ZodOptional<z.ZodString>;
213
324
  locale: z.ZodOptional<z.ZodEnum<{
214
- en: "en";
215
325
  ar: "ar";
326
+ en: "en";
216
327
  }>>;
217
328
  }, z.core.$strip>>;
218
329
  }, z.core.$strip>;
@@ -270,6 +381,7 @@ export declare const runtimeErrorSchema: z.ZodObject<{
270
381
  upstreamStatus: z.ZodOptional<z.ZodNumber>;
271
382
  }, z.core.$strip>;
272
383
  }, z.core.$strip>;
384
+ export type ToolInteraction = z.infer<typeof toolInteractionSchema>;
273
385
  export type RuntimeAgentConfig = z.infer<typeof runtimeAgentConfigSchema>;
274
386
  export type ResolveRequest = z.input<typeof resolveRequestSchema>;
275
387
  export type ResolveResponse = z.infer<typeof resolveResponseSchema>;
@@ -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;
@@ -31,9 +31,9 @@ export declare function createCcRuntime(options: CcRuntimeOptions): {
31
31
  config: {
32
32
  agentId: string;
33
33
  systemPrompt: string;
34
- promptVariables: ("userName" | "channel" | "locale")[];
34
+ promptVariables: ("userName" | "channel" | "locale" | "utcTime" | "timezone")[];
35
35
  catalogBlurb: string;
36
- defaultLocale: "en" | "ar";
36
+ defaultLocale: "ar" | "en";
37
37
  metaTools: {
38
38
  searchKnowledge: boolean;
39
39
  searchTools: boolean;
@@ -48,10 +48,11 @@ 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;
54
- locale: "en" | "ar";
55
+ locale: "ar" | "en";
55
56
  endUserToken: string;
56
57
  abortSignal: AbortSignal;
57
58
  getStepIndex: () => number;
@@ -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>;
@@ -10,7 +10,7 @@ export declare function searchRequest(cc: CcRuntime, input: {
10
10
  query: string;
11
11
  limit?: number;
12
12
  }): {
13
- locale: "en" | "ar";
13
+ locale: "ar" | "en";
14
14
  threadId: string;
15
15
  query: string;
16
16
  limit?: number;
@@ -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
  };
@@ -30,9 +33,9 @@ export declare class ControlCenterClient {
30
33
  readonly config: {
31
34
  agentId: string;
32
35
  systemPrompt: string;
33
- promptVariables: ("userName" | "channel" | "locale")[];
36
+ promptVariables: ("userName" | "channel" | "locale" | "utcTime" | "timezone")[];
34
37
  catalogBlurb: string;
35
- defaultLocale: "en" | "ar";
38
+ defaultLocale: "ar" | "en";
36
39
  metaTools: {
37
40
  searchKnowledge: boolean;
38
41
  searchTools: boolean;
@@ -51,7 +54,7 @@ export declare class ControlCenterClient {
51
54
  resolve(agentId: string, request: ResolveRequest, abortSignal?: AbortSignal): Promise<{
52
55
  resolveId: string;
53
56
  catalogVersion: string;
54
- locale: "en" | "ar";
57
+ locale: "ar" | "en";
55
58
  knowledge: {
56
59
  chunkId: string;
57
60
  text: string;
@@ -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;
@@ -2,9 +2,9 @@ import type { ControlCenterClient } from "./client";
2
2
  export declare function getControlCenterConfig(cc: ControlCenterClient, agentId: string, abortSignal?: AbortSignal): Promise<{
3
3
  agentId: string;
4
4
  systemPrompt: string;
5
- promptVariables: ("userName" | "channel" | "locale")[];
5
+ promptVariables: ("userName" | "channel" | "locale" | "utcTime" | "timezone")[];
6
6
  catalogBlurb: string;
7
- defaultLocale: "en" | "ar";
7
+ defaultLocale: "ar" | "en";
8
8
  metaTools: {
9
9
  searchKnowledge: boolean;
10
10
  searchTools: boolean;
@@ -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.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
  }
@@ -42,6 +42,18 @@ export type DatabaseAdapter = {
42
42
  messages: ChatMessage[],
43
43
  options?: { replaceAttachments?: boolean },
44
44
  ): Promise<void>;
45
+ /**
46
+ * Atomically merges one anchored interactive reference into the
47
+ * message's metadata as a single-statement JSON merge — safe under
48
+ * concurrent writers across replicas, unlike a read-modify-write of
49
+ * the whole message.
50
+ */
51
+ mergeInteractiveReference(
52
+ threadId: string,
53
+ messageId: string,
54
+ toolCallId: string,
55
+ reference: string,
56
+ ): Promise<void>;
45
57
  };
46
58
  llmRequests: {
47
59
  insert(
@@ -1,5 +1,5 @@
1
1
  // fallow-ignore-file duplicate-export -- mssql twin of the postgres repository, same factory name by design
2
- import { and, desc, eq, getColumns, inArray, type InferInsertModel } from "drizzle-orm";
2
+ import { and, desc, eq, getColumns, inArray, sql, type InferInsertModel } from "drizzle-orm";
3
3
  import { threads, messages } from "@/db/schema.mssql";
4
4
  import type { ChatMessage } from "@/types";
5
5
  import type { DatabaseAdapter } from "@/adapters/database/index";
@@ -88,5 +88,32 @@ export function createMessagesRepository(db: MssqlDb) {
88
88
  .execute();
89
89
  }
90
90
  },
91
+
92
+ async mergeInteractiveReference(threadId, messageId, toolCallId, reference) {
93
+ // JSON path keys are quoted strings; the id lands inside one.
94
+ const escapedKey = toolCallId.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
95
+ const anchorPath = `lax $.metadata.interactiveReferences."${escapedKey}"`;
96
+ await db
97
+ .update(messages)
98
+ .set({
99
+ // All JSON_QUERY reads see the row's pre-update value, so
100
+ // the statement is one atomic merge.
101
+ content: sql`JSON_MODIFY(
102
+ JSON_MODIFY(
103
+ JSON_MODIFY(
104
+ ${messages.content},
105
+ 'lax $.metadata',
106
+ JSON_QUERY(COALESCE(JSON_QUERY(${messages.content}, '$.metadata'), '{}'))
107
+ ),
108
+ 'lax $.metadata.interactiveReferences',
109
+ JSON_QUERY(COALESCE(JSON_QUERY(${messages.content}, '$.metadata.interactiveReferences'), '{}'))
110
+ ),
111
+ ${anchorPath},
112
+ ${reference}
113
+ )`,
114
+ })
115
+ .where(and(eq(messages.id, messageId), eq(messages.threadId, threadId)))
116
+ .execute();
117
+ },
91
118
  } satisfies DatabaseAdapter["messages"];
92
119
  }