@m6d/cortex-cli 1.1.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m6d/cortex-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Scaffold and operate Cortex servers",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
+ }
@@ -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>;
@@ -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 };