@m6d/cortex-server 2.0.1 → 2.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/README.md +24 -0
- package/contracts/README.md +22 -8
- package/contracts/src/client-tools/index.ts +56 -0
- package/contracts/src/graph/index.ts +30 -0
- package/contracts/{runtime.ts → src/runtime/index.ts} +18 -0
- package/contracts/{wire.ts → src/wire/index.ts} +44 -0
- package/dist/contracts/{graph.d.ts → src/graph/index.d.ts} +8 -8
- package/dist/contracts/{runtime.d.ts → src/runtime/index.d.ts} +47 -0
- package/dist/contracts/{wire.d.ts → src/wire/index.d.ts} +40 -0
- package/dist/src/lib/adapters/database/message-content.d.ts +1 -0
- package/dist/src/lib/ai/cc-runtime.d.ts +2 -1
- package/dist/src/lib/ai/client-tools.d.ts +84 -0
- package/dist/src/lib/ai/tools/query-graph.tool.d.ts +1 -1
- package/dist/src/lib/ai/turn-tools.d.ts +19 -3
- package/dist/src/lib/cc/client.d.ts +18 -0
- package/dist/src/lib/cc/registry.d.ts +14 -1
- package/dist/src/lib/cc/types.d.ts +1 -1
- package/dist/src/lib/config.d.ts +9 -4
- package/dist/src/lib/graph/index.d.ts +1 -1
- package/dist/src/lib/graph/resolver.d.ts +1 -1
- package/dist/src/lib/index.d.ts +2 -1
- package/dist/src/lib/types.d.ts +2 -2
- package/dist/src/lib/ws/connections.d.ts +1 -1
- package/package.json +6 -3
- package/src/lib/ai/cc-runtime.ts +16 -1
- package/src/lib/ai/client-tools.ts +288 -0
- package/src/lib/ai/index.ts +72 -30
- package/src/lib/ai/tools/search-tools.tool.ts +6 -2
- package/src/lib/ai/turn-tools.ts +23 -1
- 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/config.ts +8 -3
- package/src/lib/index.ts +4 -0
- package/src/lib/routes/chat.ts +23 -0
- package/tsconfig.json +1 -1
- package/contracts/graph.ts +0 -36
- /package/contracts/{graph → src/graph/clients}/embed.ts +0 -0
- /package/contracts/{graph → src/graph/clients}/neo4j.ts +0 -0
- /package/contracts/{graph → src/graph}/helpers.ts +0 -0
- /package/contracts/{graph → src/graph}/schema.ts +0 -0
- /package/contracts/{graph → src/graph}/types.ts +0 -0
- /package/contracts/{rich-text.ts → src/rich-text/index.ts} +0 -0
- /package/dist/contracts/{graph → src/graph/clients}/embed.d.ts +0 -0
- /package/dist/contracts/{graph → src/graph/clients}/neo4j.d.ts +0 -0
- /package/dist/contracts/{graph → src/graph}/helpers.d.ts +0 -0
- /package/dist/contracts/{graph → src/graph}/schema.d.ts +0 -0
- /package/dist/contracts/{graph → src/graph}/types.d.ts +0 -0
- /package/dist/contracts/{rich-text.d.ts → src/rich-text/index.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -64,6 +64,30 @@ 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
|
+
## Client tools
|
|
68
|
+
|
|
69
|
+
A tool in an agent's `tools` with an `execute` function runs on the server. One
|
|
70
|
+
without is a static client tool: it is declared to the model, the call streams
|
|
71
|
+
to the widget, and the host app answers it via `hooks.onToolCall` (or a
|
|
72
|
+
`toolComponents` entry calling `setOutput`).
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const getBrowserTimezone = toolDefinition({
|
|
76
|
+
name: "getBrowserTimezone",
|
|
77
|
+
description: "Read the user's IANA time zone from their browser.",
|
|
78
|
+
inputSchema: z.object({}),
|
|
79
|
+
}).client();
|
|
80
|
+
// agents.sample.tools = [getBrowserTimezone]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Control-Center tools published with type "embedded" are the dynamic client
|
|
84
|
+
tools: declared the same way, but the widget first calls
|
|
85
|
+
`POST /chat/:chatId/tools/:toolCallId/initiate`, which runs the tool's endpoint
|
|
86
|
+
server-to-server and hands the widget its embed payload (never the model).
|
|
87
|
+
The result the embedded page reports back is relayed to the agent as-is —
|
|
88
|
+
verifying it is the integrating backend's job. See
|
|
89
|
+
`apps/cortex-cc/docs/client-tools.md` for authoring them.
|
|
90
|
+
|
|
67
91
|
## Requirements
|
|
68
92
|
|
|
69
93
|
- Bun >= 1.0.0
|
package/contracts/README.md
CHANGED
|
@@ -4,18 +4,32 @@ The shapes both sides of a cortex boundary must agree on. Private on purpose —
|
|
|
4
4
|
it never publishes; each publishable package carries its own copy:
|
|
5
5
|
|
|
6
6
|
- `@m6d/cortex-server` and `@m6d/cortex-cli` publish raw source, so their
|
|
7
|
-
`prepack` vendors a copy of
|
|
7
|
+
`prepack` vendors a copy of `src/` into the tarball (`contracts/src/` and
|
|
8
8
|
`src/contracts/` respectively), resolved there by their shipped tsconfig paths.
|
|
9
9
|
- `@m6d/cortex-angular` and `@m6d/cortex-react` compile the parts they import
|
|
10
|
-
into their build artifacts
|
|
10
|
+
into their build artifacts (angular's `vendor` copies the seam directories
|
|
11
|
+
into `src/internal/`).
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Layout
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
Every seam is a directory: `src/<seam>/index.ts`, split into focused files
|
|
16
|
+
under the directory where that helps (see `graph/`). The `exports` map is a
|
|
17
|
+
single wildcard (`"./*": "./src/*/index.ts"`), so adding a seam is adding a
|
|
18
|
+
directory — no manifest edit, no consumer tsconfig edit for wildcard-mapped
|
|
19
|
+
consumers (server, cli, sample-server, cc).
|
|
20
|
+
|
|
21
|
+
| import | boundary | consumers |
|
|
22
|
+
| -------------------------------- | ---------------------------------------------------- | ------------------------------------------------------- |
|
|
23
|
+
| `@cortex/contracts/wire` | chat client ↔ server (HTTP/WebSocket types) | server, client SDKs (`internal/client`, angular, react) |
|
|
24
|
+
| `@cortex/contracts/client-tools` | embedded page ↔ chat widget handshake | server, client SDKs |
|
|
25
|
+
| `@cortex/contracts/runtime` | cortex-cc console ↔ server runtime API (zod schemas) | server, cortex-cc |
|
|
26
|
+
| `@cortex/contracts/rich-text` | prompt/description grammar (mentions, variables) | server, cortex-cc |
|
|
27
|
+
| `@cortex/contracts/graph` | graph authoring (cli) ↔ server (vocabulary, helpers) | server, cortex-cli |
|
|
28
|
+
|
|
29
|
+
`src/graph/clients/` is the one deliberate exception to "contracts only":
|
|
30
|
+
`neo4j.ts` and `embed.ts` are runtime I/O clients shared by the CLI and the
|
|
31
|
+
server. They live here so there is exactly one copy, vendored the same way as
|
|
32
|
+
everything else; they are re-exported through `@cortex/contracts/graph`.
|
|
19
33
|
|
|
20
34
|
Everything is consumed as TypeScript source — no build, no dist. Nothing here
|
|
21
35
|
has a runtime dependency beyond zod (for `/runtime`), and nothing here may grow
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The embed ↔ widget handshake for client 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:client-tool", 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`. The payload is the page's claim, relayed to
|
|
14
|
+
* the agent as-is: cortex does not verify it, and anything that must actually
|
|
15
|
+
* be true lives in the integrating backend's own records.
|
|
16
|
+
*
|
|
17
|
+
* Like the wire seam, 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 CLIENT_TOOL_MESSAGE_TYPE = "cortex:client-tool";
|
|
23
|
+
|
|
24
|
+
export const CLIENT_TOOL_STATUSES = ["completed", "cancelled", "failed"] as const;
|
|
25
|
+
|
|
26
|
+
export type ClientToolStatus = (typeof CLIENT_TOOL_STATUSES)[number];
|
|
27
|
+
|
|
28
|
+
export type ClientToolHandshake = {
|
|
29
|
+
type: typeof CLIENT_TOOL_MESSAGE_TYPE;
|
|
30
|
+
status: ClientToolStatus;
|
|
31
|
+
/** Opaque id for the integrating backend's own bookkeeping (session id, envelope id, …). */
|
|
32
|
+
reference?: string;
|
|
33
|
+
/** Optional payload passed through to the agent as part of the tool result. */
|
|
34
|
+
result?: unknown;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parse a `message` event into a handshake, or null when the origin doesn't
|
|
39
|
+
* match the tool's `embedOrigin` or the payload isn't a well-formed handshake.
|
|
40
|
+
* Call from the widget's `message` listener with `event.data` / `event.origin`.
|
|
41
|
+
*/
|
|
42
|
+
export function parseClientToolHandshake(data: unknown, origin: string, embedOrigin: string) {
|
|
43
|
+
if (origin !== embedOrigin) return null;
|
|
44
|
+
if (typeof data !== "object" || data === null) return null;
|
|
45
|
+
const message = data as Record<string, unknown>;
|
|
46
|
+
if (message["type"] !== CLIENT_TOOL_MESSAGE_TYPE) return null;
|
|
47
|
+
const status = CLIENT_TOOL_STATUSES.find((known) => known === message["status"]);
|
|
48
|
+
if (!status) return null;
|
|
49
|
+
const reference = message["reference"];
|
|
50
|
+
return {
|
|
51
|
+
type: CLIENT_TOOL_MESSAGE_TYPE,
|
|
52
|
+
status,
|
|
53
|
+
...(typeof reference === "string" ? { reference } : {}),
|
|
54
|
+
...(message["result"] !== undefined ? { result: message["result"] } : {}),
|
|
55
|
+
} satisfies ClientToolHandshake;
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph contract — the knowledge-graph vocabulary, authoring types and
|
|
3
|
+
* clients that `@m6d/cortex-cli` writes with and `@m6d/cortex-server` reads
|
|
4
|
+
* with. `@m6d/cortex-server` re-exports all of it, so a project authoring
|
|
5
|
+
* domains still has exactly one import path.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type { GraphSchema } from "./schema";
|
|
9
|
+
export { GRAPH_SCHEMA, GRAPH_SCHEMA_VERSION } from "./schema";
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
EndpointScalarType,
|
|
13
|
+
EndpointProperty,
|
|
14
|
+
ResponseKind,
|
|
15
|
+
AutoGenerated,
|
|
16
|
+
ConceptDef,
|
|
17
|
+
EndpointDef,
|
|
18
|
+
EndpointInput,
|
|
19
|
+
ServiceDef,
|
|
20
|
+
RuleDef,
|
|
21
|
+
DomainDef,
|
|
22
|
+
} from "./types";
|
|
23
|
+
|
|
24
|
+
export { defineConcept, defineRule, defineService, defineDomain, defineEndpoint } from "./helpers";
|
|
25
|
+
|
|
26
|
+
export type { Neo4jConfig, Neo4jClient } from "./clients/neo4j";
|
|
27
|
+
export { createNeo4jClient } from "./clients/neo4j";
|
|
28
|
+
|
|
29
|
+
export type { EmbedFn, EmbeddingProviderConfig } from "./clients/embed";
|
|
30
|
+
export { createEmbedder } from "./clients/embed";
|
|
@@ -89,6 +89,18 @@ export const knowledgeChunkSchema = z.object({
|
|
|
89
89
|
}),
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Runtime contract §5.4: present on signatures of `embedded` 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
|
+
* the result the page reports back is relayed as-is — verifying it is the
|
|
97
|
+
* integrating backend's job.
|
|
98
|
+
*/
|
|
99
|
+
export const toolEmbedSchema = z.object({
|
|
100
|
+
surface: z.enum(["inline", "modal"]),
|
|
101
|
+
embedOrigin: z.url(),
|
|
102
|
+
});
|
|
103
|
+
|
|
92
104
|
export const toolSignatureSchema = z.object({
|
|
93
105
|
toolId: z.uuid(),
|
|
94
106
|
name: z.string().regex(TOOL_NAME_PATTERN),
|
|
@@ -98,6 +110,11 @@ export const toolSignatureSchema = z.object({
|
|
|
98
110
|
signature: z.string(),
|
|
99
111
|
score: z.number().nullable(),
|
|
100
112
|
pinned: z.boolean(),
|
|
113
|
+
embed: toolEmbedSchema.optional(),
|
|
114
|
+
/** Client tools only: the published input JSON Schema, verbatim.
|
|
115
|
+
* Client-executed declarations need a real schema — the rendered
|
|
116
|
+
* `signature` text alone is not enough for function calling. */
|
|
117
|
+
inputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
101
118
|
});
|
|
102
119
|
|
|
103
120
|
export const serviceCardSchema = z.object({
|
|
@@ -236,6 +253,7 @@ export const runtimeErrorSchema = z.object({
|
|
|
236
253
|
}),
|
|
237
254
|
});
|
|
238
255
|
|
|
256
|
+
export type ToolEmbed = z.infer<typeof toolEmbedSchema>;
|
|
239
257
|
export type RuntimeAgentConfig = z.infer<typeof runtimeAgentConfigSchema>;
|
|
240
258
|
export type ResolveRequest = z.input<typeof resolveRequestSchema>;
|
|
241
259
|
export type ResolveResponse = z.infer<typeof resolveResponseSchema>;
|
|
@@ -70,12 +70,24 @@ export type TokenUsage = {
|
|
|
70
70
|
total: number;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* How to launch one client 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 ClientToolBinding = {
|
|
79
|
+
toolId: string;
|
|
80
|
+
surface: "inline" | "modal";
|
|
81
|
+
embedOrigin: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
73
84
|
export type MessageMetadata = {
|
|
74
85
|
modelId?: string;
|
|
75
86
|
providerMetadata?: unknown;
|
|
76
87
|
isAborted?: boolean;
|
|
77
88
|
tokenUsage?: TokenUsage;
|
|
78
89
|
attachments?: AttachmentSummary[];
|
|
90
|
+
clientTools?: Record<string, ClientToolBinding>;
|
|
79
91
|
};
|
|
80
92
|
|
|
81
93
|
/**
|
|
@@ -93,6 +105,38 @@ export type CortexMessage<TPart = unknown> = {
|
|
|
93
105
|
metadata?: MessageMetadata;
|
|
94
106
|
};
|
|
95
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Key under the AG-UI request's `forwardedProps` carrying the answers to
|
|
110
|
+
* parked client tool calls on a continuation request. The server owns the
|
|
111
|
+
* transcript (a continuation sends `messages: []`), so the answered assistant
|
|
112
|
+
* message itself never crosses the wire — the SDK's park-boundary snapshot
|
|
113
|
+
* does not preserve its id, and round-tripping it duplicated the stored
|
|
114
|
+
* message.
|
|
115
|
+
*/
|
|
116
|
+
export const CLIENT_TOOL_ANSWERS_KEY = "clientToolAnswers";
|
|
117
|
+
|
|
118
|
+
/** One answered client tool call, applied to the stored transcript server-side. */
|
|
119
|
+
export type ClientToolAnswer = {
|
|
120
|
+
toolCallId: string;
|
|
121
|
+
output: unknown;
|
|
122
|
+
state: "complete" | "error";
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Response of `POST /chat/:chatId/tools/:toolCallId/initiate`. `embed:
|
|
127
|
+
* false` means the pending call is not a CC client tool — the widget
|
|
128
|
+
* falls back to its default rendering for the call. The payload is for the
|
|
129
|
+
* widget only; it never reaches the LLM.
|
|
130
|
+
*/
|
|
131
|
+
export type ClientToolInitiateResult =
|
|
132
|
+
| { embed: false }
|
|
133
|
+
| {
|
|
134
|
+
embed: true;
|
|
135
|
+
embedUrl: string;
|
|
136
|
+
surface: "inline" | "modal";
|
|
137
|
+
embedOrigin: string;
|
|
138
|
+
};
|
|
139
|
+
|
|
96
140
|
export type ThreadCreatedEvent = {
|
|
97
141
|
type: "thread:created";
|
|
98
142
|
payload: { thread: ThreadSummary };
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* with. `@m6d/cortex-server` re-exports all of it, so a project authoring
|
|
5
5
|
* domains still has exactly one import path.
|
|
6
6
|
*/
|
|
7
|
-
export type { GraphSchema } from "./
|
|
8
|
-
export { GRAPH_SCHEMA, GRAPH_SCHEMA_VERSION } from "./
|
|
9
|
-
export type { EndpointScalarType, EndpointProperty, ResponseKind, AutoGenerated, ConceptDef, EndpointDef, EndpointInput, ServiceDef, RuleDef, DomainDef, } from "./
|
|
10
|
-
export { defineConcept, defineRule, defineService, defineDomain, defineEndpoint
|
|
11
|
-
export type { Neo4jConfig, Neo4jClient } from "./
|
|
12
|
-
export { createNeo4jClient } from "./
|
|
13
|
-
export type { EmbedFn, EmbeddingProviderConfig } from "./
|
|
14
|
-
export { createEmbedder } from "./
|
|
7
|
+
export type { GraphSchema } from "./schema";
|
|
8
|
+
export { GRAPH_SCHEMA, GRAPH_SCHEMA_VERSION } from "./schema";
|
|
9
|
+
export type { EndpointScalarType, EndpointProperty, ResponseKind, AutoGenerated, ConceptDef, EndpointDef, EndpointInput, ServiceDef, RuleDef, DomainDef, } from "./types";
|
|
10
|
+
export { defineConcept, defineRule, defineService, defineDomain, defineEndpoint } from "./helpers";
|
|
11
|
+
export type { Neo4jConfig, Neo4jClient } from "./clients/neo4j";
|
|
12
|
+
export { createNeo4jClient } from "./clients/neo4j";
|
|
13
|
+
export type { EmbedFn, EmbeddingProviderConfig } from "./clients/embed";
|
|
14
|
+
export { createEmbedder } from "./clients/embed";
|
|
@@ -60,6 +60,20 @@ 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 `embedded` 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
|
+
* the result the page reports back is relayed as-is — verifying it is the
|
|
68
|
+
* integrating backend's job.
|
|
69
|
+
*/
|
|
70
|
+
export declare const toolEmbedSchema: z.ZodObject<{
|
|
71
|
+
surface: z.ZodEnum<{
|
|
72
|
+
inline: "inline";
|
|
73
|
+
modal: "modal";
|
|
74
|
+
}>;
|
|
75
|
+
embedOrigin: z.ZodURL;
|
|
76
|
+
}, z.core.$strip>;
|
|
63
77
|
export declare const toolSignatureSchema: z.ZodObject<{
|
|
64
78
|
toolId: z.ZodUUID;
|
|
65
79
|
name: z.ZodString;
|
|
@@ -69,6 +83,14 @@ export declare const toolSignatureSchema: z.ZodObject<{
|
|
|
69
83
|
signature: z.ZodString;
|
|
70
84
|
score: z.ZodNullable<z.ZodNumber>;
|
|
71
85
|
pinned: z.ZodBoolean;
|
|
86
|
+
embed: z.ZodOptional<z.ZodObject<{
|
|
87
|
+
surface: z.ZodEnum<{
|
|
88
|
+
inline: "inline";
|
|
89
|
+
modal: "modal";
|
|
90
|
+
}>;
|
|
91
|
+
embedOrigin: z.ZodURL;
|
|
92
|
+
}, z.core.$strip>>;
|
|
93
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
72
94
|
}, z.core.$strip>;
|
|
73
95
|
export declare const serviceCardSchema: z.ZodObject<{
|
|
74
96
|
serviceId: z.ZodUUID;
|
|
@@ -152,6 +174,14 @@ export declare const resolveResponseSchema: z.ZodObject<{
|
|
|
152
174
|
signature: z.ZodString;
|
|
153
175
|
score: z.ZodNullable<z.ZodNumber>;
|
|
154
176
|
pinned: z.ZodBoolean;
|
|
177
|
+
embed: z.ZodOptional<z.ZodObject<{
|
|
178
|
+
surface: z.ZodEnum<{
|
|
179
|
+
inline: "inline";
|
|
180
|
+
modal: "modal";
|
|
181
|
+
}>;
|
|
182
|
+
embedOrigin: z.ZodURL;
|
|
183
|
+
}, z.core.$strip>>;
|
|
184
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
155
185
|
}, z.core.$strip>>;
|
|
156
186
|
services: z.ZodArray<z.ZodObject<{
|
|
157
187
|
serviceId: z.ZodUUID;
|
|
@@ -191,6 +221,14 @@ export declare const searchToolsResponseSchema: z.ZodObject<{
|
|
|
191
221
|
signature: z.ZodString;
|
|
192
222
|
score: z.ZodNullable<z.ZodNumber>;
|
|
193
223
|
pinned: z.ZodBoolean;
|
|
224
|
+
embed: z.ZodOptional<z.ZodObject<{
|
|
225
|
+
surface: z.ZodEnum<{
|
|
226
|
+
inline: "inline";
|
|
227
|
+
modal: "modal";
|
|
228
|
+
}>;
|
|
229
|
+
embedOrigin: z.ZodURL;
|
|
230
|
+
}, z.core.$strip>>;
|
|
231
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
194
232
|
}, z.core.$strip>>;
|
|
195
233
|
sharedShapes: z.ZodArray<z.ZodObject<{
|
|
196
234
|
ref: z.ZodString;
|
|
@@ -215,6 +253,14 @@ export declare const searchServicesResponseSchema: z.ZodObject<{
|
|
|
215
253
|
signature: z.ZodString;
|
|
216
254
|
score: z.ZodNullable<z.ZodNumber>;
|
|
217
255
|
pinned: z.ZodBoolean;
|
|
256
|
+
embed: z.ZodOptional<z.ZodObject<{
|
|
257
|
+
surface: z.ZodEnum<{
|
|
258
|
+
inline: "inline";
|
|
259
|
+
modal: "modal";
|
|
260
|
+
}>;
|
|
261
|
+
embedOrigin: z.ZodURL;
|
|
262
|
+
}, z.core.$strip>>;
|
|
263
|
+
inputSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
218
264
|
}, z.core.$strip>>;
|
|
219
265
|
sharedShapes: z.ZodArray<z.ZodObject<{
|
|
220
266
|
ref: z.ZodString;
|
|
@@ -302,6 +348,7 @@ export declare const runtimeErrorSchema: z.ZodObject<{
|
|
|
302
348
|
upstreamStatus: z.ZodOptional<z.ZodNumber>;
|
|
303
349
|
}, z.core.$strip>;
|
|
304
350
|
}, z.core.$strip>;
|
|
351
|
+
export type ToolEmbed = z.infer<typeof toolEmbedSchema>;
|
|
305
352
|
export type RuntimeAgentConfig = z.infer<typeof runtimeAgentConfigSchema>;
|
|
306
353
|
export type ResolveRequest = z.input<typeof resolveRequestSchema>;
|
|
307
354
|
export type ResolveResponse = z.infer<typeof resolveResponseSchema>;
|
|
@@ -50,12 +50,23 @@ export type TokenUsage = {
|
|
|
50
50
|
};
|
|
51
51
|
total: number;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* How to launch one client 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 ClientToolBinding = {
|
|
59
|
+
toolId: string;
|
|
60
|
+
surface: "inline" | "modal";
|
|
61
|
+
embedOrigin: string;
|
|
62
|
+
};
|
|
53
63
|
export type MessageMetadata = {
|
|
54
64
|
modelId?: string;
|
|
55
65
|
providerMetadata?: unknown;
|
|
56
66
|
isAborted?: boolean;
|
|
57
67
|
tokenUsage?: TokenUsage;
|
|
58
68
|
attachments?: AttachmentSummary[];
|
|
69
|
+
clientTools?: Record<string, ClientToolBinding>;
|
|
59
70
|
};
|
|
60
71
|
/**
|
|
61
72
|
* A stored message as it is persisted and served. TanStack AI's `UIMessage`
|
|
@@ -71,6 +82,35 @@ export type CortexMessage<TPart = unknown> = {
|
|
|
71
82
|
parts: TPart[];
|
|
72
83
|
metadata?: MessageMetadata;
|
|
73
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Key under the AG-UI request's `forwardedProps` carrying the answers to
|
|
87
|
+
* parked client tool calls on a continuation request. The server owns the
|
|
88
|
+
* transcript (a continuation sends `messages: []`), so the answered assistant
|
|
89
|
+
* message itself never crosses the wire — the SDK's park-boundary snapshot
|
|
90
|
+
* does not preserve its id, and round-tripping it duplicated the stored
|
|
91
|
+
* message.
|
|
92
|
+
*/
|
|
93
|
+
export declare const CLIENT_TOOL_ANSWERS_KEY = "clientToolAnswers";
|
|
94
|
+
/** One answered client tool call, applied to the stored transcript server-side. */
|
|
95
|
+
export type ClientToolAnswer = {
|
|
96
|
+
toolCallId: string;
|
|
97
|
+
output: unknown;
|
|
98
|
+
state: "complete" | "error";
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Response of `POST /chat/:chatId/tools/:toolCallId/initiate`. `embed:
|
|
102
|
+
* false` means the pending call is not a CC client tool — the widget
|
|
103
|
+
* falls back to its default rendering for the call. The payload is for the
|
|
104
|
+
* widget only; it never reaches the LLM.
|
|
105
|
+
*/
|
|
106
|
+
export type ClientToolInitiateResult = {
|
|
107
|
+
embed: false;
|
|
108
|
+
} | {
|
|
109
|
+
embed: true;
|
|
110
|
+
embedUrl: string;
|
|
111
|
+
surface: "inline" | "modal";
|
|
112
|
+
embedOrigin: string;
|
|
113
|
+
};
|
|
74
114
|
export type ThreadCreatedEvent = {
|
|
75
115
|
type: "thread:created";
|
|
76
116
|
payload: {
|
|
@@ -16,6 +16,7 @@ export declare function withOwnedAttachments(messages: ChatMessage[], replaceAtt
|
|
|
16
16
|
providerMetadata?: unknown;
|
|
17
17
|
isAborted?: boolean;
|
|
18
18
|
tokenUsage?: import("../../types").TokenUsage;
|
|
19
|
+
clientTools?: Record<string, import("../..").ClientToolBinding>;
|
|
19
20
|
};
|
|
20
21
|
id: string;
|
|
21
22
|
role: "system" | "user" | "assistant";
|
|
@@ -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 { CcClientTool, 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
|
+
clientTools: Map<string, CcClientTool>;
|
|
51
52
|
threadId: string;
|
|
52
53
|
turnKey: string;
|
|
53
54
|
userId: string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ClientToolAnswer } from "../../../contracts/src/wire";
|
|
2
|
+
import type { ResolvedCortexAgentConfig } from "../config";
|
|
3
|
+
import type { ChatMessage, Thread } from "../types";
|
|
4
|
+
import { ControlCenterClient } from "../cc/client";
|
|
5
|
+
import type { CcRuntime } from "../cc/registry";
|
|
6
|
+
/**
|
|
7
|
+
* The wire-shape bindings stamped on the turn's final assistant message, so a
|
|
8
|
+
* parked client tool call can be initiated after any restart. Only names that
|
|
9
|
+
* actually won declaration merging are stamped — a call to a colliding
|
|
10
|
+
* consumer-owned tool must never be treated as a client tool. Undefined when
|
|
11
|
+
* nothing qualifies, keeping metadata lean.
|
|
12
|
+
*/
|
|
13
|
+
export declare function clientToolBindings(cc: CcRuntime | undefined, takenNames: ReadonlySet<string>): {
|
|
14
|
+
[k: string]: {
|
|
15
|
+
surface: "inline" | "modal";
|
|
16
|
+
embedOrigin: string;
|
|
17
|
+
toolId: string;
|
|
18
|
+
};
|
|
19
|
+
} | undefined;
|
|
20
|
+
/** Null when the agent has no Control Center — every caller treats that as "skip". */
|
|
21
|
+
export declare function createCcClient(config: ResolvedCortexAgentConfig): ControlCenterClient | null;
|
|
22
|
+
/**
|
|
23
|
+
* The `clientToolAnswers` a continuation request carried under
|
|
24
|
+
* `forwardedProps`, validated structurally — the widget is not the only
|
|
25
|
+
* possible author of a request body. All-or-nothing: `[]` means the request
|
|
26
|
+
* carried no answers at all, while `null` means it carried a payload with any
|
|
27
|
+
* malformed entry — the caller must not act on such a request, neither by
|
|
28
|
+
* settling its valid remainder nor by running a turn for it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function clientToolAnswersFrom(forwardedProps: Record<string, unknown>): {
|
|
31
|
+
toolCallId: string;
|
|
32
|
+
output: unknown;
|
|
33
|
+
state: "complete" | "error";
|
|
34
|
+
}[] | null;
|
|
35
|
+
/**
|
|
36
|
+
* Applies client tool answers to the stored messages carrying their calls,
|
|
37
|
+
* mirroring the SDK's own settle shape (call part gains output + state, a
|
|
38
|
+
* tool-result part is appended). Already-settled calls are skipped, so a
|
|
39
|
+
* replayed continuation is a no-op. Returns only the messages that changed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function answerStoredToolCalls(stored: ChatMessage[], answers: ClientToolAnswer[]): {
|
|
42
|
+
parts: import("@tanstack/ai").MessagePart[];
|
|
43
|
+
id: string;
|
|
44
|
+
role: "system" | "user" | "assistant";
|
|
45
|
+
metadata?: import("../types").MessageMetadata;
|
|
46
|
+
}[];
|
|
47
|
+
/**
|
|
48
|
+
* The server-authoritative half of a client tool continuation: the widget
|
|
49
|
+
* sends `messages: []` plus the answers, and the transcript is updated in
|
|
50
|
+
* place — the answered assistant message never round-trips, so the id the
|
|
51
|
+
* SDK's park-boundary snapshot failed to preserve no longer matters.
|
|
52
|
+
* `settled` is how many messages the answers changed; with `replied` it lets
|
|
53
|
+
* the caller tell a true replay (nothing settled, reply already stored) from
|
|
54
|
+
* a retry after a continuation that died before replying.
|
|
55
|
+
*/
|
|
56
|
+
export declare function applyClientToolAnswers(config: ResolvedCortexAgentConfig, userId: string, threadId: string, answers: ClientToolAnswer[]): Promise<{
|
|
57
|
+
settled: number;
|
|
58
|
+
replied: boolean;
|
|
59
|
+
}>;
|
|
60
|
+
/**
|
|
61
|
+
* `POST /chat/:chatId/tools/:toolCallId/initiate` — runs the interactive
|
|
62
|
+
* tool's initiate call (the tool's own endpoint) and hands the widget its
|
|
63
|
+
* embed payload. The model never sees this payload. The idempotency key is
|
|
64
|
+
* pinned to the tool call, so a reload mid-flow reuses the created session
|
|
65
|
+
* instead of opening a second one. The result the page later posts back is
|
|
66
|
+
* relayed to the agent as-is — verifying it is the integrating backend's job.
|
|
67
|
+
*/
|
|
68
|
+
export declare function initiateClientTool(options: {
|
|
69
|
+
config: ResolvedCortexAgentConfig;
|
|
70
|
+
thread: Thread;
|
|
71
|
+
userId: string;
|
|
72
|
+
token: string;
|
|
73
|
+
toolCallId: string;
|
|
74
|
+
}): Promise<{
|
|
75
|
+
embed: false;
|
|
76
|
+
embedUrl?: undefined;
|
|
77
|
+
surface?: undefined;
|
|
78
|
+
embedOrigin?: undefined;
|
|
79
|
+
} | {
|
|
80
|
+
embed: true;
|
|
81
|
+
embedUrl: string;
|
|
82
|
+
surface: "inline" | "modal";
|
|
83
|
+
embedOrigin: string;
|
|
84
|
+
}>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import z from "zod";
|
|
2
|
-
import type { Neo4jClient } from "../../../../contracts/graph";
|
|
2
|
+
import type { Neo4jClient } from "../../../../contracts/src/graph";
|
|
3
3
|
export declare function createQueryGraphTool(neo4j: Neo4jClient): import("@tanstack/ai").ServerTool<z.ZodObject<{
|
|
4
4
|
query: z.ZodString;
|
|
5
5
|
parameters: z.ZodOptional<z.ZodString>;
|
|
@@ -2,7 +2,7 @@ import type { AnyServerTool } from "@tanstack/ai";
|
|
|
2
2
|
import type { ResolvedCortexAgentConfig } from "../config";
|
|
3
3
|
import type { Attachment, Thread } from "../types";
|
|
4
4
|
import type { CcRuntime } from "../cc/registry";
|
|
5
|
-
import type { Neo4jClient } from "../../../contracts/graph";
|
|
5
|
+
import type { Neo4jClient } from "../../../contracts/src/graph";
|
|
6
6
|
type TurnToolsOptions = {
|
|
7
7
|
config: ResolvedCortexAgentConfig;
|
|
8
8
|
cc: CcRuntime | undefined;
|
|
@@ -19,9 +19,11 @@ type TurnToolsOptions = {
|
|
|
19
19
|
* configured, then the agent's own tools.
|
|
20
20
|
*
|
|
21
21
|
* User tools are appended last and deliberately win on name collision — an agent
|
|
22
|
-
* must be able to replace a built-in it doesn't want.
|
|
22
|
+
* must be able to replace a built-in it doesn't want. A user tool without an
|
|
23
|
+
* `execute` function is a static client tool: it passes through to the model
|
|
24
|
+
* declaration untouched, and the runtime streams its calls to the widget.
|
|
23
25
|
*/
|
|
24
|
-
export declare function buildTurnTools(options: TurnToolsOptions): AnyServerTool[];
|
|
26
|
+
export declare function buildTurnTools(options: TurnToolsOptions): (AnyServerTool | import("@tanstack/ai").AnyClientTool)[];
|
|
25
27
|
/**
|
|
26
28
|
* Instruments tool calls for the callbacks an agent configured. Returns
|
|
27
29
|
* `undefined` when nothing is listening, so the caller can skip the middleware
|
|
@@ -35,5 +37,19 @@ export declare function createToolInstrumentation(options: TurnToolsOptions & {
|
|
|
35
37
|
onBeforeToolCall(_ctx: import("@tanstack/ai").ChatMiddlewareContext<unknown>, { toolName, toolCallId, args }: import("@tanstack/ai").ToolCallHookContext): void;
|
|
36
38
|
onAfterToolCall(_ctx: import("@tanstack/ai").ChatMiddlewareContext<unknown>, { toolName, toolCallId }: import("@tanstack/ai").AfterToolCallInfo): void;
|
|
37
39
|
} | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* CC client tools are declared like the request's client tools: no
|
|
42
|
+
* executor, so the call streams to the widget as a tool-call part and the run
|
|
43
|
+
* parks until the widget answers. The declaration carries the tool's published
|
|
44
|
+
* input schema — function-calling models ignore schemas described only in
|
|
45
|
+
* prose — with a permissive object as the fallback for older Control Centers.
|
|
46
|
+
* Names the consumer already claimed are skipped: those calls belong to the
|
|
47
|
+
* consumer's tool, and must neither be declared nor stamped as client tools.
|
|
48
|
+
*/
|
|
49
|
+
export declare function clientToolDeclarations(cc: CcRuntime | undefined, takenNames: ReadonlySet<string>): {
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
parameters: Record<string, unknown>;
|
|
53
|
+
}[];
|
|
38
54
|
export declare function hasDefaultAttachmentInterceptor(config: ResolvedCortexAgentConfig): boolean;
|
|
39
55
|
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 initiate per tool call across reloads. */
|
|
13
|
+
idempotencyKey?: string;
|
|
11
14
|
timeoutMs?: number;
|
|
12
15
|
abortSignal?: AbortSignal;
|
|
13
16
|
};
|
|
@@ -74,6 +77,11 @@ export declare class ControlCenterClient {
|
|
|
74
77
|
signature: string;
|
|
75
78
|
score: number | null;
|
|
76
79
|
pinned: boolean;
|
|
80
|
+
embed?: {
|
|
81
|
+
surface: "inline" | "modal";
|
|
82
|
+
embedOrigin: string;
|
|
83
|
+
} | undefined;
|
|
84
|
+
inputSchema?: Record<string, unknown> | undefined;
|
|
77
85
|
}[];
|
|
78
86
|
services: {
|
|
79
87
|
serviceId: string;
|
|
@@ -103,6 +111,11 @@ export declare class ControlCenterClient {
|
|
|
103
111
|
signature: string;
|
|
104
112
|
score: number | null;
|
|
105
113
|
pinned: boolean;
|
|
114
|
+
embed?: {
|
|
115
|
+
surface: "inline" | "modal";
|
|
116
|
+
embedOrigin: string;
|
|
117
|
+
} | undefined;
|
|
118
|
+
inputSchema?: Record<string, unknown> | undefined;
|
|
106
119
|
}[];
|
|
107
120
|
sharedShapes: {
|
|
108
121
|
ref: string;
|
|
@@ -127,6 +140,11 @@ export declare class ControlCenterClient {
|
|
|
127
140
|
signature: string;
|
|
128
141
|
score: number | null;
|
|
129
142
|
pinned: boolean;
|
|
143
|
+
embed?: {
|
|
144
|
+
surface: "inline" | "modal";
|
|
145
|
+
embedOrigin: string;
|
|
146
|
+
} | undefined;
|
|
147
|
+
inputSchema?: Record<string, unknown> | undefined;
|
|
130
148
|
}[];
|
|
131
149
|
sharedShapes: {
|
|
132
150
|
ref: string;
|