@m6d/cortex-server 1.5.0 → 1.7.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 CHANGED
@@ -57,7 +57,7 @@ export default {
57
57
 
58
58
  `cortex.serve()` is async — it runs database migrations on startup.
59
59
 
60
- Each key in `agents` becomes a route prefix (e.g. `assistant` → `/agents/assistant/...`). Agents can define per-agent `systemPrompt`, `tools`, `backendFetch`, `loadSessionData`, and lifecycle hooks (`onToolCall`, `onStreamFinish`).
60
+ 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`).
61
61
 
62
62
  ## Requirements
63
63
 
@@ -1,4 +1,4 @@
1
1
  import type { ResolvedCortexAgentConfig } from "../config.ts";
2
2
  import type { Thread } from "../types.ts";
3
- export declare function stream(messages: unknown[], thread: Thread, userId: string, token: string, config: ResolvedCortexAgentConfig): Promise<Response>;
3
+ export declare function stream(messages: unknown[], thread: Thread, userId: string, token: string, requestContext: Record<string, unknown>, config: ResolvedCortexAgentConfig): Promise<Response>;
4
4
  export declare function generateTitle(threadId: string, prompt: string, userId: string, config: ResolvedCortexAgentConfig): Promise<void>;
@@ -1,9 +1,9 @@
1
1
  import type { ResolvedContext } from "../graph/resolver.ts";
2
- import type { ResolvedCortexAgentConfig } from "../config.ts";
2
+ import type { PromptContext, ResolvedCortexAgentConfig } from "../config.ts";
3
3
  import type { Thread } from "../types.ts";
4
4
  /**
5
5
  * Resolves session data for the thread, loading from the configured
6
6
  * session loader if not already cached on the thread.
7
7
  */
8
8
  export declare function resolveSession(config: ResolvedCortexAgentConfig, thread: Thread, token: string): Promise<Record<string, unknown> | null>;
9
- export declare function buildSystemPrompt(config: ResolvedCortexAgentConfig, resolved: ResolvedContext | null, session: Record<string, unknown> | null): Promise<string>;
9
+ export declare function buildSystemPrompt(config: ResolvedCortexAgentConfig, resolved: ResolvedContext | null, promptContext: PromptContext): Promise<string>;
@@ -10,6 +10,10 @@ export type KnowledgeConfig = {
10
10
  };
11
11
  domains?: Record<string, DomainDef>;
12
12
  };
13
+ export type PromptContext<TSession extends Record<string, unknown> = Record<string, unknown>, TRequestContext extends Record<string, unknown> = Record<string, unknown>> = {
14
+ session: TSession | null;
15
+ requestContext: TRequestContext;
16
+ };
13
17
  export type DatabaseConfig = {
14
18
  type: "mssql";
15
19
  connectionString: string;
@@ -22,8 +26,8 @@ export type StorageConfig = {
22
26
  secretKey: string;
23
27
  bucketName?: string;
24
28
  };
25
- export type CortexAgentDefinition = {
26
- systemPrompt: string | ((session: Record<string, unknown> | null) => string | Promise<string>);
29
+ export type CortexAgentDefinition<TSession extends Record<string, unknown> = Record<string, unknown>, TRequestContext extends Record<string, unknown> = Record<string, unknown>> = {
30
+ systemPrompt: string | ((context: PromptContext<TSession, TRequestContext>) => string | Promise<string>);
27
31
  tools?: ToolSet;
28
32
  backendFetch?: {
29
33
  baseUrl: string;
@@ -34,7 +38,8 @@ export type CortexAgentDefinition = {
34
38
  }) => Promise<Record<string, unknown>>;
35
39
  interceptor?: RequestInterceptorOptions;
36
40
  };
37
- loadSessionData?: (token: string) => Promise<Record<string, unknown>>;
41
+ loadSessionData?: (token: string) => Promise<TSession>;
42
+ resolveRequestContext?: (request: Request) => TRequestContext | Promise<TRequestContext>;
38
43
  onToolCall?: (toolCall: {
39
44
  toolName: string;
40
45
  toolCallId: string;
@@ -92,7 +97,7 @@ export type ResolvedCortexAgentConfig = {
92
97
  url: string;
93
98
  apiKey: string;
94
99
  };
95
- systemPrompt: string | ((session: Record<string, unknown> | null) => string | Promise<string>);
100
+ systemPrompt: string | ((context: PromptContext) => string | Promise<string>);
96
101
  tools?: ToolSet;
97
102
  backendFetch?: {
98
103
  baseUrl: string;
@@ -104,6 +109,7 @@ export type ResolvedCortexAgentConfig = {
104
109
  interceptor?: RequestInterceptorOptions;
105
110
  };
106
111
  loadSessionData?: (token: string) => Promise<Record<string, unknown>>;
112
+ resolveRequestContext?: (request: Request) => Record<string, unknown> | Promise<Record<string, unknown>>;
107
113
  onToolCall?: (toolCall: {
108
114
  toolName: string;
109
115
  toolCallId: string;
@@ -151,3 +157,9 @@ export type CortexConfig = {
151
157
  knowledge?: KnowledgeConfig;
152
158
  agents: Record<string, CortexAgentDefinition>;
153
159
  };
160
+ /**
161
+ * Helper to define an agent with full type inference for `systemPrompt` context.
162
+ * The `context.session` type is inferred from `loadSessionData`'s return type,
163
+ * and `context.requestContext` is inferred from `resolveRequestContext`'s return type.
164
+ */
165
+ export declare function defineAgent<TSession extends Record<string, unknown> = Record<string, unknown>, TRequestContext extends Record<string, unknown> = Record<string, unknown>>(config: CortexAgentDefinition<TSession, TRequestContext>): CortexAgentDefinition;
@@ -1,4 +1,5 @@
1
- export type { CortexConfig, CortexAgentDefinition, KnowledgeConfig, DatabaseConfig, StorageConfig, } from "./config";
1
+ export type { CortexConfig, CortexAgentDefinition, KnowledgeConfig, DatabaseConfig, StorageConfig, PromptContext, } from "./config";
2
+ export { defineAgent } from "./config";
2
3
  export type { ContextConfig, ThreadContextMeta } from "./ai/context/types";
3
4
  export type { Thread, AppEnv } from "./types";
4
5
  export type { CortexInstance } from "./factory";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m6d/cortex-server",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Reusable AI agent chat server library for Hono + Bun",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/ai/index.ts CHANGED
@@ -36,6 +36,7 @@ export async function stream(
36
36
  thread: Thread,
37
37
  userId: string,
38
38
  token: string,
39
+ requestContext: Record<string, unknown>,
39
40
  config: ResolvedCortexAgentConfig,
40
41
  ) {
41
42
  const abortController = new AbortController();
@@ -111,7 +112,10 @@ export async function stream(
111
112
  ...config.tools,
112
113
  } as ToolSet;
113
114
 
114
- const systemPrompt = await buildSystemPrompt(config, resolved, session);
115
+ const systemPrompt = await buildSystemPrompt(config, resolved, {
116
+ session,
117
+ requestContext,
118
+ });
115
119
 
116
120
  // The context builder reserved a static token budget for the system prompt + tools.
117
121
  // Now that we have the actual values, verify the reserve was sufficient and trim
package/src/ai/prompt.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ResolvedContext } from "../graph/resolver.ts";
2
- import type { ResolvedCortexAgentConfig } from "../config.ts";
2
+ import type { PromptContext, ResolvedCortexAgentConfig } from "../config.ts";
3
3
  import type { Thread } from "../types.ts";
4
4
 
5
5
  /**
@@ -26,12 +26,12 @@ export async function resolveSession(
26
26
  export async function buildSystemPrompt(
27
27
  config: ResolvedCortexAgentConfig,
28
28
  resolved: ResolvedContext | null,
29
- session: Record<string, unknown> | null,
29
+ promptContext: PromptContext,
30
30
  ) {
31
31
  // Resolve the consumer's base system prompt
32
32
  let basePrompt: string;
33
33
  if (typeof config.systemPrompt === "function") {
34
- basePrompt = await config.systemPrompt(session);
34
+ basePrompt = await config.systemPrompt(promptContext);
35
35
  } else {
36
36
  basePrompt = config.systemPrompt;
37
37
  }
package/src/config.ts CHANGED
@@ -10,6 +10,14 @@ export type KnowledgeConfig = {
10
10
  domains?: Record<string, DomainDef>;
11
11
  };
12
12
 
13
+ export type PromptContext<
14
+ TSession extends Record<string, unknown> = Record<string, unknown>,
15
+ TRequestContext extends Record<string, unknown> = Record<string, unknown>,
16
+ > = {
17
+ session: TSession | null;
18
+ requestContext: TRequestContext;
19
+ };
20
+
13
21
  export type DatabaseConfig = {
14
22
  type: "mssql";
15
23
  connectionString: string;
@@ -24,8 +32,13 @@ export type StorageConfig = {
24
32
  bucketName?: string;
25
33
  };
26
34
 
27
- export type CortexAgentDefinition = {
28
- systemPrompt: string | ((session: Record<string, unknown> | null) => string | Promise<string>);
35
+ export type CortexAgentDefinition<
36
+ TSession extends Record<string, unknown> = Record<string, unknown>,
37
+ TRequestContext extends Record<string, unknown> = Record<string, unknown>,
38
+ > = {
39
+ systemPrompt:
40
+ | string
41
+ | ((context: PromptContext<TSession, TRequestContext>) => string | Promise<string>);
29
42
  tools?: ToolSet;
30
43
  backendFetch?: {
31
44
  baseUrl: string;
@@ -37,7 +50,8 @@ export type CortexAgentDefinition = {
37
50
  ) => Promise<Record<string, unknown>>;
38
51
  interceptor?: RequestInterceptorOptions;
39
52
  };
40
- loadSessionData?: (token: string) => Promise<Record<string, unknown>>;
53
+ loadSessionData?: (token: string) => Promise<TSession>;
54
+ resolveRequestContext?: (request: Request) => TRequestContext | Promise<TRequestContext>;
41
55
  onToolCall?: (toolCall: {
42
56
  toolName: string;
43
57
  toolCallId: string;
@@ -93,7 +107,7 @@ export type ResolvedCortexAgentConfig = {
93
107
  url: string;
94
108
  apiKey: string;
95
109
  };
96
- systemPrompt: string | ((session: Record<string, unknown> | null) => string | Promise<string>);
110
+ systemPrompt: string | ((context: PromptContext) => string | Promise<string>);
97
111
  tools?: ToolSet;
98
112
  backendFetch?: {
99
113
  baseUrl: string;
@@ -106,6 +120,9 @@ export type ResolvedCortexAgentConfig = {
106
120
  interceptor?: RequestInterceptorOptions;
107
121
  };
108
122
  loadSessionData?: (token: string) => Promise<Record<string, unknown>>;
123
+ resolveRequestContext?: (
124
+ request: Request,
125
+ ) => Record<string, unknown> | Promise<Record<string, unknown>>;
109
126
  onToolCall?: (toolCall: {
110
127
  toolName: string;
111
128
  toolCallId: string;
@@ -151,3 +168,15 @@ export type CortexConfig = {
151
168
  knowledge?: KnowledgeConfig;
152
169
  agents: Record<string, CortexAgentDefinition>;
153
170
  };
171
+
172
+ /**
173
+ * Helper to define an agent with full type inference for `systemPrompt` context.
174
+ * The `context.session` type is inferred from `loadSessionData`'s return type,
175
+ * and `context.requestContext` is inferred from `resolveRequestContext`'s return type.
176
+ */
177
+ export function defineAgent<
178
+ TSession extends Record<string, unknown> = Record<string, unknown>,
179
+ TRequestContext extends Record<string, unknown> = Record<string, unknown>,
180
+ >(config: CortexAgentDefinition<TSession, TRequestContext>): CortexAgentDefinition {
181
+ return config as CortexAgentDefinition;
182
+ }
package/src/factory.ts CHANGED
@@ -95,6 +95,7 @@ export function createCortex(config: CortexConfig) {
95
95
  tools: agentDef.tools,
96
96
  backendFetch: agentDef.backendFetch,
97
97
  loadSessionData: agentDef.loadSessionData,
98
+ resolveRequestContext: agentDef.resolveRequestContext,
98
99
  onToolCall: agentDef.onToolCall,
99
100
  onStreamFinish: agentDef.onStreamFinish,
100
101
  context: resolvedContext,
package/src/index.ts CHANGED
@@ -5,7 +5,9 @@ export type {
5
5
  KnowledgeConfig,
6
6
  DatabaseConfig,
7
7
  StorageConfig,
8
+ PromptContext,
8
9
  } from "./config";
10
+ export { defineAgent } from "./config";
9
11
 
10
12
  export type { ContextConfig, ThreadContextMeta } from "./ai/context/types";
11
13
 
@@ -27,6 +27,9 @@ export function createChatRoutes() {
27
27
  const agentId = c.get("agentId");
28
28
  const { id: userId, token } = c.get("user");
29
29
  const { messages, id: threadId } = c.req.valid("json");
30
+ const requestContext = config.resolveRequestContext
31
+ ? await config.resolveRequestContext(c.req.raw)
32
+ : {};
30
33
 
31
34
  const thread = await config.db.threads.getById(userId, threadId);
32
35
 
@@ -34,7 +37,7 @@ export function createChatRoutes() {
34
37
  throw new HTTPException(404, { message: "Not found" });
35
38
  }
36
39
 
37
- return stream(messages, thread, userId, token, config);
40
+ return stream(messages, thread, userId, token, requestContext, config);
38
41
  },
39
42
  );
40
43