@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.
Files changed (50) hide show
  1. package/README.md +24 -0
  2. package/contracts/README.md +22 -8
  3. package/contracts/src/client-tools/index.ts +56 -0
  4. package/contracts/src/graph/index.ts +30 -0
  5. package/contracts/{runtime.ts → src/runtime/index.ts} +18 -0
  6. package/contracts/{wire.ts → src/wire/index.ts} +44 -0
  7. package/dist/contracts/{graph.d.ts → src/graph/index.d.ts} +8 -8
  8. package/dist/contracts/{runtime.d.ts → src/runtime/index.d.ts} +47 -0
  9. package/dist/contracts/{wire.d.ts → src/wire/index.d.ts} +40 -0
  10. package/dist/src/lib/adapters/database/message-content.d.ts +1 -0
  11. package/dist/src/lib/ai/cc-runtime.d.ts +2 -1
  12. package/dist/src/lib/ai/client-tools.d.ts +84 -0
  13. package/dist/src/lib/ai/tools/query-graph.tool.d.ts +1 -1
  14. package/dist/src/lib/ai/turn-tools.d.ts +19 -3
  15. package/dist/src/lib/cc/client.d.ts +18 -0
  16. package/dist/src/lib/cc/registry.d.ts +14 -1
  17. package/dist/src/lib/cc/types.d.ts +1 -1
  18. package/dist/src/lib/config.d.ts +9 -4
  19. package/dist/src/lib/graph/index.d.ts +1 -1
  20. package/dist/src/lib/graph/resolver.d.ts +1 -1
  21. package/dist/src/lib/index.d.ts +2 -1
  22. package/dist/src/lib/types.d.ts +2 -2
  23. package/dist/src/lib/ws/connections.d.ts +1 -1
  24. package/package.json +6 -3
  25. package/src/lib/ai/cc-runtime.ts +16 -1
  26. package/src/lib/ai/client-tools.ts +288 -0
  27. package/src/lib/ai/index.ts +72 -30
  28. package/src/lib/ai/tools/search-tools.tool.ts +6 -2
  29. package/src/lib/ai/turn-tools.ts +23 -1
  30. package/src/lib/cc/client.ts +5 -1
  31. package/src/lib/cc/format.ts +5 -2
  32. package/src/lib/cc/registry.ts +16 -2
  33. package/src/lib/cc/types.ts +1 -0
  34. package/src/lib/config.ts +8 -3
  35. package/src/lib/index.ts +4 -0
  36. package/src/lib/routes/chat.ts +23 -0
  37. package/tsconfig.json +1 -1
  38. package/contracts/graph.ts +0 -36
  39. /package/contracts/{graph → src/graph/clients}/embed.ts +0 -0
  40. /package/contracts/{graph → src/graph/clients}/neo4j.ts +0 -0
  41. /package/contracts/{graph → src/graph}/helpers.ts +0 -0
  42. /package/contracts/{graph → src/graph}/schema.ts +0 -0
  43. /package/contracts/{graph → src/graph}/types.ts +0 -0
  44. /package/contracts/{rich-text.ts → src/rich-text/index.ts} +0 -0
  45. /package/dist/contracts/{graph → src/graph/clients}/embed.d.ts +0 -0
  46. /package/dist/contracts/{graph → src/graph/clients}/neo4j.d.ts +0 -0
  47. /package/dist/contracts/{graph → src/graph}/helpers.d.ts +0 -0
  48. /package/dist/contracts/{graph → src/graph}/schema.d.ts +0 -0
  49. /package/dist/contracts/{graph → src/graph}/types.d.ts +0 -0
  50. /package/dist/contracts/{rich-text.d.ts → src/rich-text/index.d.ts} +0 -0
@@ -29,7 +29,9 @@ type TurnToolsOptions = {
29
29
  * configured, then the agent's own tools.
30
30
  *
31
31
  * User tools are appended last and deliberately win on name collision — an agent
32
- * must be able to replace a built-in it doesn't want.
32
+ * must be able to replace a built-in it doesn't want. A user tool without an
33
+ * `execute` function is a static client tool: it passes through to the model
34
+ * declaration untouched, and the runtime streams its calls to the widget.
33
35
  */
34
36
  export function buildTurnTools(options: TurnToolsOptions) {
35
37
  const { config, cc, neo4j, thread, userId, token, session, requestContext, threadAttachments } =
@@ -110,6 +112,26 @@ export function createToolInstrumentation(
110
112
  } satisfies ChatMiddleware;
111
113
  }
112
114
 
115
+ /**
116
+ * CC client tools are declared like the request's client tools: no
117
+ * executor, so the call streams to the widget as a tool-call part and the run
118
+ * parks until the widget answers. The declaration carries the tool's published
119
+ * input schema — function-calling models ignore schemas described only in
120
+ * prose — with a permissive object as the fallback for older Control Centers.
121
+ * Names the consumer already claimed are skipped: those calls belong to the
122
+ * consumer's tool, and must neither be declared nor stamped as client tools.
123
+ */
124
+ export function clientToolDeclarations(cc: CcRuntime | undefined, takenNames: ReadonlySet<string>) {
125
+ if (!cc) return [];
126
+ return [...cc.clientTools]
127
+ .filter(([name]) => !takenNames.has(name))
128
+ .map(([name, tool]) => ({
129
+ name,
130
+ description: tool.signature,
131
+ parameters: tool.inputSchema ?? { type: "object", additionalProperties: true },
132
+ }));
133
+ }
134
+
113
135
  function createCcTools(cc: CcRuntime | undefined) {
114
136
  if (!cc) return [];
115
137
 
@@ -21,6 +21,9 @@ export type ExecuteOptions = {
21
21
  turnKey: string;
22
22
  stepIndex: number;
23
23
  callIndex: number;
24
+ /** Overrides the composed key — used where stability must outlive the turn
25
+ * counters, e.g. one initiate per tool call across reloads. */
26
+ idempotencyKey?: string;
24
27
  timeoutMs?: number;
25
28
  abortSignal?: AbortSignal;
26
29
  };
@@ -138,7 +141,8 @@ export class ControlCenterClient {
138
141
  if (!options.readOnly) {
139
142
  headers.set(
140
143
  "Idempotency-Key",
141
- `${options.threadId}:${options.turnKey}:${options.stepIndex}:${options.callIndex}`,
144
+ options.idempotencyKey ??
145
+ `${options.threadId}:${options.turnKey}:${options.stepIndex}:${options.callIndex}`,
142
146
  );
143
147
  }
144
148
 
@@ -74,10 +74,13 @@ export function buildCcSection(cc: CcPromptInput) {
74
74
  if (cc.resolved.sharedShapes.length > 0) {
75
75
  parts.push(`## Response Shapes\n${formatSharedShapes(cc.resolved.sharedShapes)}`);
76
76
  }
77
- if (cc.resolved.tools.length > 0) {
77
+ // Client tools are declared to the model as real tools, so they
78
+ // stay out of the sandbox-only Dynamic Tools section.
79
+ const dynamicTools = cc.resolved.tools.filter((tool) => !tool.embed);
80
+ if (dynamicTools.length > 0) {
78
81
  parts.push(
79
82
  "## Dynamic Tools\nCall these from executeCode via the `tools` global, e.g. `await tools.name(input)`.\n\n" +
80
- formatToolSignatures(cc.resolved.tools),
83
+ formatToolSignatures(dynamicTools),
81
84
  );
82
85
  }
83
86
  if (cc.resolved.services.length > 0) {
@@ -1,11 +1,23 @@
1
1
  import type { ControlCenterClient } from "./client";
2
- import type { RuntimeAgentConfig } from "./types";
2
+ import type { RuntimeAgentConfig, ToolEmbed } from "./types";
3
3
 
4
4
  export type CcToolBinding = {
5
5
  toolId: string;
6
6
  readOnly: boolean;
7
7
  };
8
8
 
9
+ /**
10
+ * A client tool resolved for this turn. Declared to the model as a real
11
+ * client-executed tool — never a sandbox binding — so its call streams to the
12
+ * widget as a tool-call part and parks the run until the flow settles.
13
+ */
14
+ export type CcClientTool = {
15
+ toolId: string;
16
+ signature: string;
17
+ embed: ToolEmbed;
18
+ inputSchema?: Record<string, unknown>;
19
+ };
20
+
9
21
  /**
10
22
  * Turn-scoped and mutable: seeded from /resolve, extended by searchTools
11
23
  * mid-turn so a tool discovered at step 3 is callable at step 4.
@@ -14,9 +26,10 @@ export type CcToolRegistry = Map<string, CcToolBinding>;
14
26
 
15
27
  export function registerCcTools(
16
28
  registry: CcToolRegistry,
17
- tools: { name: string; toolId: string; readOnly: boolean }[],
29
+ tools: { name: string; toolId: string; readOnly: boolean; embed?: ToolEmbed }[],
18
30
  ) {
19
31
  for (const tool of tools) {
32
+ if (tool.embed) continue;
20
33
  registry.set(tool.name, { toolId: tool.toolId, readOnly: tool.readOnly });
21
34
  }
22
35
  }
@@ -50,6 +63,7 @@ export type CcRuntime = {
50
63
  agentId: string;
51
64
  config: RuntimeAgentConfig;
52
65
  registry: CcToolRegistry;
66
+ clientTools: Map<string, CcClientTool>;
53
67
  threadId: string;
54
68
  turnKey: string;
55
69
  userId: string;
@@ -17,6 +17,7 @@ export {
17
17
  type ResolveResponse,
18
18
  type RuntimeAgentConfig,
19
19
  type SearchRequest,
20
+ type ToolEmbed,
20
21
  } from "@cortex/contracts/runtime";
21
22
 
22
23
  const knownRuntimeErrorKinds = new Set<string>(RUNTIME_ERROR_KINDS);
package/src/lib/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AnyServerTool } from "@tanstack/ai";
1
+ import type { AnyClientTool, AnyServerTool } from "@tanstack/ai";
2
2
  import type { DatabaseAdapter } from "./adapters/database/index";
3
3
  import type { StorageAdapter } from "./adapters/storage/index";
4
4
  import type { DomainDef } from "@cortex/contracts/graph";
@@ -6,8 +6,13 @@ import type { RequestInterceptorOptions } from "./ai/interceptors/request-interc
6
6
  import type { ContextConfig } from "./ai/context/types";
7
7
  import type { ChatMessage, Thread } from "./types";
8
8
 
9
- /** An agent's tools, as `chat()` takes them. */
10
- export type ToolSet = ReadonlyArray<AnyServerTool>;
9
+ /**
10
+ * An agent's tools, as `chat()` takes them. A tool with an `execute` function
11
+ * runs on the server; one without is a static client tool — declared to the
12
+ * model, streamed to the widget as a tool-call part, and answered by the host
13
+ * app (`hooks.onToolCall` or a `toolComponents` entry with `setOutput`).
14
+ */
15
+ export type ToolSet = ReadonlyArray<AnyServerTool | AnyClientTool>;
11
16
 
12
17
  type ModelConfig = {
13
18
  baseURL: string;
package/src/lib/index.ts CHANGED
@@ -33,6 +33,10 @@ export type {
33
33
  } from "./ai/interceptors/request-interceptor";
34
34
  export { createRequestInterceptor } from "./ai/interceptors/request-interceptor";
35
35
 
36
+ // Re-exported so consumers define tools (server or static client) without a
37
+ // direct @tanstack/ai import.
38
+ export { toolDefinition } from "@tanstack/ai";
39
+
36
40
  // Tools (consumers may register custom tools or use built-in ones)
37
41
  export { createQueryGraphTool } from "./ai/tools/query-graph.tool";
38
42
  export { createExecuteCodeTool } from "./ai/tools/execute-code.tool";
@@ -6,6 +6,7 @@ import { HTTPException } from "hono/http-exception";
6
6
  import type { CortexAppEnv } from "@/types";
7
7
  import { requireAuth } from "@/auth/middleware";
8
8
  import { startTurn } from "@/ai/index";
9
+ import { initiateClientTool } from "@/ai/client-tools";
9
10
  import { abortRun, getRunId, joinLog, offsetBelongsToRun } from "@/ai/active-runs";
10
11
  import { notify } from "@/ws/connections";
11
12
  import { requireOwnedThread } from "./owned-thread";
@@ -91,6 +92,28 @@ export function createChatRoutes() {
91
92
  return resumeServerSentEventsResponse({ adapter: joinLog(chatId, runId) });
92
93
  });
93
94
 
95
+ /**
96
+ * Runs a client tool's initiate call for a pending tool call and
97
+ * returns the widget's embed payload. `embed: false` tells the
98
+ * widget the call is not an CC client tool — fall back to its
99
+ * default rendering. The payload never reaches the model.
100
+ */
101
+ app.post("/chat/:chatId/tools/:toolCallId/initiate", requireAuth, async function (c) {
102
+ const config = c.get("agentConfig");
103
+ const { id: userId, token } = c.get("user");
104
+ const thread = await requireOwnedThread(c, c.req.param("chatId"));
105
+
106
+ return c.json(
107
+ await initiateClientTool({
108
+ config,
109
+ thread,
110
+ userId,
111
+ token,
112
+ toolCallId: c.req.param("toolCallId"),
113
+ }),
114
+ );
115
+ });
116
+
94
117
  app.post("/chat/:chatId/abort", requireAuth, async function (c) {
95
118
  const agentId = c.get("agentId");
96
119
  const userId = c.get("user").id;
package/tsconfig.json CHANGED
@@ -23,7 +23,7 @@
23
23
  // `vendor` copies them into ./contracts for build and pack. The first
24
24
  // candidate exists only then (and in the published tarball) — inside
25
25
  // the repo resolution falls through to the workspace source.
26
- "@cortex/contracts/*": ["./contracts/*", "../../internal/contracts/*"],
26
+ "@cortex/contracts/*": ["./contracts/src/*", "../../internal/contracts/src/*"],
27
27
  // Internal-only. tsc does not rewrite aliases on emit, so `build` runs
28
28
  // tsc-alias to turn these back into relative paths in dist/**/*.d.ts —
29
29
  // a consumer's tsc knows nothing about our `@/`. The published raw src
@@ -1,36 +0,0 @@
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 "./graph/schema";
9
- export { GRAPH_SCHEMA, GRAPH_SCHEMA_VERSION } from "./graph/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 "./graph/types";
23
-
24
- export {
25
- defineConcept,
26
- defineRule,
27
- defineService,
28
- defineDomain,
29
- defineEndpoint,
30
- } from "./graph/helpers";
31
-
32
- export type { Neo4jConfig, Neo4jClient } from "./graph/neo4j";
33
- export { createNeo4jClient } from "./graph/neo4j";
34
-
35
- export type { EmbedFn, EmbeddingProviderConfig } from "./graph/embed";
36
- export { createEmbedder } from "./graph/embed";
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes