@inkeep/agents-run-api 0.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 (149) hide show
  1. package/README.md +117 -0
  2. package/dist/AgentExecutionServer.d.ts +23 -0
  3. package/dist/AgentExecutionServer.d.ts.map +1 -0
  4. package/dist/AgentExecutionServer.js +32 -0
  5. package/dist/__tests__/setup.d.ts +4 -0
  6. package/dist/__tests__/setup.d.ts.map +1 -0
  7. package/dist/__tests__/setup.js +50 -0
  8. package/dist/__tests__/utils/testProject.d.ts +18 -0
  9. package/dist/__tests__/utils/testProject.d.ts.map +1 -0
  10. package/dist/__tests__/utils/testProject.js +26 -0
  11. package/dist/__tests__/utils/testRequest.d.ts +8 -0
  12. package/dist/__tests__/utils/testRequest.d.ts.map +1 -0
  13. package/dist/__tests__/utils/testRequest.js +32 -0
  14. package/dist/__tests__/utils/testTenant.d.ts +64 -0
  15. package/dist/__tests__/utils/testTenant.d.ts.map +1 -0
  16. package/dist/__tests__/utils/testTenant.js +71 -0
  17. package/dist/a2a/client.d.ts +182 -0
  18. package/dist/a2a/client.d.ts.map +1 -0
  19. package/dist/a2a/client.js +645 -0
  20. package/dist/a2a/handlers.d.ts +4 -0
  21. package/dist/a2a/handlers.d.ts.map +1 -0
  22. package/dist/a2a/handlers.js +657 -0
  23. package/dist/a2a/transfer.d.ts +18 -0
  24. package/dist/a2a/transfer.d.ts.map +1 -0
  25. package/dist/a2a/transfer.js +22 -0
  26. package/dist/a2a/types.d.ts +63 -0
  27. package/dist/a2a/types.d.ts.map +1 -0
  28. package/dist/a2a/types.js +1 -0
  29. package/dist/agents/Agent.d.ts +154 -0
  30. package/dist/agents/Agent.d.ts.map +1 -0
  31. package/dist/agents/Agent.js +1105 -0
  32. package/dist/agents/ModelFactory.d.ts +62 -0
  33. package/dist/agents/ModelFactory.d.ts.map +1 -0
  34. package/dist/agents/ModelFactory.js +208 -0
  35. package/dist/agents/SystemPromptBuilder.d.ts +14 -0
  36. package/dist/agents/SystemPromptBuilder.d.ts.map +1 -0
  37. package/dist/agents/SystemPromptBuilder.js +62 -0
  38. package/dist/agents/ToolSessionManager.d.ts +61 -0
  39. package/dist/agents/ToolSessionManager.d.ts.map +1 -0
  40. package/dist/agents/ToolSessionManager.js +143 -0
  41. package/dist/agents/artifactTools.d.ts +30 -0
  42. package/dist/agents/artifactTools.d.ts.map +1 -0
  43. package/dist/agents/artifactTools.js +463 -0
  44. package/dist/agents/generateTaskHandler.d.ts +41 -0
  45. package/dist/agents/generateTaskHandler.d.ts.map +1 -0
  46. package/dist/agents/generateTaskHandler.js +350 -0
  47. package/dist/agents/relationTools.d.ts +33 -0
  48. package/dist/agents/relationTools.d.ts.map +1 -0
  49. package/dist/agents/relationTools.js +245 -0
  50. package/dist/agents/types.d.ts +23 -0
  51. package/dist/agents/types.d.ts.map +1 -0
  52. package/dist/agents/types.js +1 -0
  53. package/dist/agents/versions/V1Config.d.ts +21 -0
  54. package/dist/agents/versions/V1Config.d.ts.map +1 -0
  55. package/dist/agents/versions/V1Config.js +285 -0
  56. package/dist/app.d.ts +4 -0
  57. package/dist/app.d.ts.map +1 -0
  58. package/dist/app.js +194 -0
  59. package/dist/data/agentGraph.d.ts +4 -0
  60. package/dist/data/agentGraph.d.ts.map +1 -0
  61. package/dist/data/agentGraph.js +73 -0
  62. package/dist/data/agents.d.ts +4 -0
  63. package/dist/data/agents.d.ts.map +1 -0
  64. package/dist/data/agents.js +73 -0
  65. package/dist/data/conversations.d.ts +59 -0
  66. package/dist/data/conversations.d.ts.map +1 -0
  67. package/dist/data/conversations.js +216 -0
  68. package/dist/data/db/clean.d.ts +6 -0
  69. package/dist/data/db/clean.d.ts.map +1 -0
  70. package/dist/data/db/clean.js +77 -0
  71. package/dist/data/db/dbClient.d.ts +3 -0
  72. package/dist/data/db/dbClient.d.ts.map +1 -0
  73. package/dist/data/db/dbClient.js +13 -0
  74. package/dist/env.d.ts +43 -0
  75. package/dist/env.d.ts.map +1 -0
  76. package/dist/env.js +63 -0
  77. package/dist/handlers/executionHandler.d.ts +36 -0
  78. package/dist/handlers/executionHandler.d.ts.map +1 -0
  79. package/dist/handlers/executionHandler.js +402 -0
  80. package/dist/index.d.ts +5 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +43 -0
  83. package/dist/instrumentation.d.ts +13 -0
  84. package/dist/instrumentation.d.ts.map +1 -0
  85. package/dist/instrumentation.js +66 -0
  86. package/dist/logger.d.ts +4 -0
  87. package/dist/logger.d.ts.map +1 -0
  88. package/dist/logger.js +32 -0
  89. package/dist/middleware/api-key-auth.d.ts +22 -0
  90. package/dist/middleware/api-key-auth.d.ts.map +1 -0
  91. package/dist/middleware/api-key-auth.js +139 -0
  92. package/dist/middleware/index.d.ts +2 -0
  93. package/dist/middleware/index.d.ts.map +1 -0
  94. package/dist/middleware/index.js +1 -0
  95. package/dist/openapi.d.ts +2 -0
  96. package/dist/openapi.d.ts.map +1 -0
  97. package/dist/openapi.js +36 -0
  98. package/dist/routes/agents.d.ts +4 -0
  99. package/dist/routes/agents.d.ts.map +1 -0
  100. package/dist/routes/agents.js +155 -0
  101. package/dist/routes/chat.d.ts +4 -0
  102. package/dist/routes/chat.d.ts.map +1 -0
  103. package/dist/routes/chat.js +308 -0
  104. package/dist/routes/chatDataStream.d.ts +4 -0
  105. package/dist/routes/chatDataStream.d.ts.map +1 -0
  106. package/dist/routes/chatDataStream.js +179 -0
  107. package/dist/routes/mcp.d.ts +4 -0
  108. package/dist/routes/mcp.d.ts.map +1 -0
  109. package/dist/routes/mcp.js +500 -0
  110. package/dist/tracer.d.ts +24 -0
  111. package/dist/tracer.d.ts.map +1 -0
  112. package/dist/tracer.js +97 -0
  113. package/dist/types/chat.d.ts +25 -0
  114. package/dist/types/chat.d.ts.map +1 -0
  115. package/dist/types/chat.js +1 -0
  116. package/dist/types/execution-context.d.ts +14 -0
  117. package/dist/types/execution-context.d.ts.map +1 -0
  118. package/dist/types/execution-context.js +14 -0
  119. package/dist/utils/agent-operations.d.ts +79 -0
  120. package/dist/utils/agent-operations.d.ts.map +1 -0
  121. package/dist/utils/agent-operations.js +67 -0
  122. package/dist/utils/artifact-component-schema.d.ts +29 -0
  123. package/dist/utils/artifact-component-schema.d.ts.map +1 -0
  124. package/dist/utils/artifact-component-schema.js +119 -0
  125. package/dist/utils/artifact-parser.d.ts +71 -0
  126. package/dist/utils/artifact-parser.d.ts.map +1 -0
  127. package/dist/utils/artifact-parser.js +251 -0
  128. package/dist/utils/cleanup.d.ts +19 -0
  129. package/dist/utils/cleanup.d.ts.map +1 -0
  130. package/dist/utils/cleanup.js +66 -0
  131. package/dist/utils/data-component-schema.d.ts +6 -0
  132. package/dist/utils/data-component-schema.d.ts.map +1 -0
  133. package/dist/utils/data-component-schema.js +43 -0
  134. package/dist/utils/graph-session.d.ts +200 -0
  135. package/dist/utils/graph-session.d.ts.map +1 -0
  136. package/dist/utils/graph-session.js +1009 -0
  137. package/dist/utils/incremental-stream-parser.d.ts +57 -0
  138. package/dist/utils/incremental-stream-parser.d.ts.map +1 -0
  139. package/dist/utils/incremental-stream-parser.js +287 -0
  140. package/dist/utils/response-formatter.d.ts +27 -0
  141. package/dist/utils/response-formatter.d.ts.map +1 -0
  142. package/dist/utils/response-formatter.js +160 -0
  143. package/dist/utils/stream-helpers.d.ts +162 -0
  144. package/dist/utils/stream-helpers.d.ts.map +1 -0
  145. package/dist/utils/stream-helpers.js +385 -0
  146. package/dist/utils/stream-registry.d.ts +18 -0
  147. package/dist/utils/stream-registry.d.ts.map +1 -0
  148. package/dist/utils/stream-registry.js +33 -0
  149. package/package.json +88 -0
@@ -0,0 +1,139 @@
1
+ import { createMiddleware } from 'hono/factory';
2
+ import { HTTPException } from 'hono/http-exception';
3
+ import { validateAndGetApiKey, getLogger } from '@inkeep/agents-core';
4
+ import dbClient from '../data/db/dbClient.js';
5
+ import { createExecutionContext } from '../types/execution-context.js';
6
+ import { env } from '../env.js';
7
+ const logger = getLogger('env-key-auth');
8
+ /**
9
+ * Middleware to authenticate API requests using Bearer token authentication
10
+ * First checks if token matches INKEEP_AGENTS_RUN_BYPASS_SECRET, then falls back to API key validation
11
+ * Extracts and validates API keys, then adds execution context to the request
12
+ */
13
+ export const apiKeyAuth = () => createMiddleware(async (c, next) => {
14
+ const authHeader = c.req.header('Authorization');
15
+ const tenantId = c.req.header('x-inkeep-tenant-id');
16
+ const projectId = c.req.header('x-inkeep-project-id');
17
+ const graphId = c.req.header('x-inkeep-graph-id');
18
+ const agentId = c.req.header('x-inkeep-agent-id');
19
+ const baseUrl = new URL(c.req.url).origin;
20
+ // Bypass authentication only for integration tests with specific header
21
+ if (process.env.ENVIRONMENT === 'development' || process.env.ENVIRONMENT === 'test') {
22
+ const executionContext = createExecutionContext({
23
+ apiKey: 'development',
24
+ tenantId: tenantId || 'test-tenant',
25
+ projectId: projectId || 'test-project',
26
+ graphId: graphId || 'test-graph',
27
+ apiKeyId: 'test-key',
28
+ baseUrl: baseUrl,
29
+ agentId: agentId,
30
+ });
31
+ c.set('executionContext', executionContext);
32
+ logger.info({}, 'Test environment bypass authenticated successfully');
33
+ await next();
34
+ return;
35
+ }
36
+ // Check for Bearer token
37
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
38
+ throw new HTTPException(401, {
39
+ message: 'Missing or invalid authorization header. Expected: Bearer <api_key>',
40
+ });
41
+ }
42
+ const apiKey = authHeader.substring(7); // Remove 'Bearer ' prefix
43
+ // If bypass secret is configured, allow bypass authentication or api key validation
44
+ if (env.INKEEP_AGENTS_RUN_BYPASS_SECRET) {
45
+ if (apiKey === env.INKEEP_AGENTS_RUN_BYPASS_SECRET) {
46
+ // Extract base URL from request
47
+ if (!tenantId || !projectId || !graphId) {
48
+ throw new HTTPException(401, {
49
+ message: 'Missing or invalid tenant, project, or graph ID',
50
+ });
51
+ }
52
+ // Create bypass execution context with default values
53
+ const executionContext = createExecutionContext({
54
+ apiKey: apiKey,
55
+ tenantId: tenantId,
56
+ projectId: projectId,
57
+ graphId: graphId,
58
+ apiKeyId: 'bypass',
59
+ baseUrl: baseUrl,
60
+ agentId: agentId,
61
+ });
62
+ c.set('executionContext', executionContext);
63
+ logger.info({}, 'Bypass secret authenticated successfully');
64
+ await next();
65
+ return;
66
+ }
67
+ else if (apiKey) {
68
+ const executionContext = await extractContextFromApiKey(apiKey);
69
+ c.set('executionContext', executionContext);
70
+ logger.info({}, 'API key authenticated successfully');
71
+ await next();
72
+ return;
73
+ }
74
+ else {
75
+ // Bypass secret is set but token doesn't match - reject
76
+ throw new HTTPException(401, {
77
+ message: 'Invalid Token',
78
+ });
79
+ }
80
+ }
81
+ // No bypass secret configured - continue with normal API key validation
82
+ // Validate API key format (basic validation)
83
+ if (!apiKey || apiKey.length < 16) {
84
+ throw new HTTPException(401, {
85
+ message: 'Invalid API key format',
86
+ });
87
+ }
88
+ try {
89
+ const executionContext = await extractContextFromApiKey(apiKey);
90
+ c.set('executionContext', executionContext);
91
+ // Log successful authentication (without sensitive data)
92
+ logger.debug({
93
+ tenantId: executionContext.tenantId,
94
+ projectId: executionContext.projectId,
95
+ graphId: executionContext.graphId,
96
+ }, 'API key authenticated successfully');
97
+ await next();
98
+ }
99
+ catch (error) {
100
+ // Re-throw HTTPException
101
+ if (error instanceof HTTPException) {
102
+ throw error;
103
+ }
104
+ // Log unexpected errors and return generic message
105
+ logger.error({ error }, 'API key authentication error');
106
+ throw new HTTPException(500, {
107
+ message: 'Authentication failed',
108
+ });
109
+ }
110
+ });
111
+ export const extractContextFromApiKey = async (apiKey) => {
112
+ const apiKeyRecord = await validateAndGetApiKey(apiKey, dbClient);
113
+ if (!apiKeyRecord) {
114
+ throw new HTTPException(401, {
115
+ message: 'Invalid or expired API key',
116
+ });
117
+ }
118
+ return createExecutionContext({
119
+ apiKey: apiKey,
120
+ tenantId: apiKeyRecord.tenantId,
121
+ projectId: apiKeyRecord.projectId,
122
+ graphId: apiKeyRecord.graphId,
123
+ apiKeyId: apiKeyRecord.id,
124
+ });
125
+ };
126
+ /**
127
+ * Helper middleware for endpoints that optionally support API key authentication
128
+ * If no auth header is present, it continues without setting the executionContext
129
+ */
130
+ export const optionalAuth = () => createMiddleware(async (c, next) => {
131
+ const authHeader = c.req.header('Authorization');
132
+ // If no auth header, continue without authentication
133
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
134
+ await next();
135
+ return;
136
+ }
137
+ // If auth header exists, use the regular auth middleware
138
+ return apiKeyAuth()(c, next);
139
+ });
@@ -0,0 +1,2 @@
1
+ export * from './api-key-auth.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './api-key-auth.js';
@@ -0,0 +1,2 @@
1
+ export declare function setupOpenAPIRoutes(app: any): void;
2
+ //# sourceMappingURL=openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,GAAG,QAsC1C"}
@@ -0,0 +1,36 @@
1
+ import { swaggerUI } from '@hono/swagger-ui';
2
+ import { env } from './env.js';
3
+ export function setupOpenAPIRoutes(app) {
4
+ // OpenAPI specification endpoint - serves the complete API spec
5
+ app.get('/openapi.json', (c) => {
6
+ try {
7
+ const document = app.getOpenAPIDocument({
8
+ openapi: '3.0.0',
9
+ info: {
10
+ title: 'Inkeep Execution API',
11
+ version: '1.0.0',
12
+ description: 'Complete REST API for Inkeep Execution application including chat completions, A2A agent communication, and comprehensive CRUD operations for all entities',
13
+ },
14
+ servers: [
15
+ {
16
+ url: env.AGENT_BASE_URL || `http://localhost:3003`,
17
+ description: 'Development server',
18
+ },
19
+ ],
20
+ });
21
+ return c.json(document);
22
+ }
23
+ catch (error) {
24
+ console.error('OpenAPI document generation failed:', error);
25
+ const errorDetails = error instanceof Error
26
+ ? { message: error.message, stack: error.stack }
27
+ : JSON.stringify(error, null, 2);
28
+ return c.json({ error: 'Failed to generate OpenAPI document', details: errorDetails }, 500);
29
+ }
30
+ });
31
+ // Swagger UI endpoint for interactive documentation
32
+ app.get('/docs', swaggerUI({
33
+ url: '/openapi.json',
34
+ title: 'Inkeep Execution API Documentation',
35
+ }));
36
+ }
@@ -0,0 +1,4 @@
1
+ import { OpenAPIHono } from '@hono/zod-openapi';
2
+ declare const app: OpenAPIHono<import("hono").Env, {}, "/">;
3
+ export default app;
4
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/routes/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAc7D,QAAA,MAAM,GAAG,0CAAoB,CAAC;AAkM9B,eAAe,GAAG,CAAC"}
@@ -0,0 +1,155 @@
1
+ import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
2
+ import { z } from 'zod';
3
+ import { a2aHandler } from '../a2a/handlers.js';
4
+ import { getAgentGraphWithDefaultAgent, getRequestExecutionContext, HeadersScopeSchema, } from '@inkeep/agents-core';
5
+ import { getRegisteredGraph } from '../data/agentGraph.js';
6
+ import { getRegisteredAgent } from '../data/agents.js';
7
+ import { getLogger } from '../logger.js';
8
+ import dbClient from '../data/db/dbClient.js';
9
+ const app = new OpenAPIHono();
10
+ const logger = getLogger('agents');
11
+ // A2A Agent Card Discovery (REST with OpenAPI)
12
+ app.openapi(createRoute({
13
+ method: 'get',
14
+ path: '/.well-known/agent.json',
15
+ request: {
16
+ headers: HeadersScopeSchema,
17
+ },
18
+ tags: ['a2a'],
19
+ security: [{ bearerAuth: [] }],
20
+ responses: {
21
+ 200: {
22
+ description: 'Agent Card for A2A discovery',
23
+ content: {
24
+ 'application/json': {
25
+ schema: z.object({
26
+ name: z.string(),
27
+ description: z.string().optional(),
28
+ url: z.string(),
29
+ version: z.string(),
30
+ defaultInputModes: z.array(z.string()),
31
+ defaultOutputModes: z.array(z.string()),
32
+ skills: z.array(z.any()),
33
+ }),
34
+ },
35
+ },
36
+ },
37
+ 404: {
38
+ description: 'Agent not found',
39
+ },
40
+ },
41
+ }), async (c) => {
42
+ const otelHeaders = {
43
+ traceparent: c.req.header('traceparent'),
44
+ tracestate: c.req.header('tracestate'),
45
+ baggage: c.req.header('baggage'),
46
+ };
47
+ logger.info({
48
+ otelHeaders,
49
+ path: c.req.path,
50
+ method: c.req.method,
51
+ }, 'OpenTelemetry headers: well-known agent.json');
52
+ // Get execution context from API key authentication
53
+ const executionContext = getRequestExecutionContext(c);
54
+ const { tenantId, projectId, graphId, agentId } = executionContext;
55
+ // If agentId is defined in execution context, run agent-level logic
56
+ if (agentId) {
57
+ logger.info({
58
+ message: 'getRegisteredAgent (agent-level)',
59
+ tenantId,
60
+ projectId,
61
+ graphId,
62
+ agentId,
63
+ }, 'agent-level well-known agent.json');
64
+ const agent = await getRegisteredAgent(executionContext);
65
+ logger.info({ agent }, 'agent registered: well-known agent.json');
66
+ if (!agent) {
67
+ return c.json({ error: 'Agent not found' }, 404);
68
+ }
69
+ return c.json(agent.agentCard);
70
+ }
71
+ else {
72
+ // Run graph-level logic
73
+ logger.info({
74
+ message: 'getRegisteredGraph (graph-level)',
75
+ tenantId,
76
+ projectId,
77
+ graphId,
78
+ }, 'graph-level well-known agent.json');
79
+ const graph = await getRegisteredGraph(executionContext);
80
+ if (!graph) {
81
+ return c.json({ error: 'Graph not found' }, 404);
82
+ }
83
+ return c.json(graph.agentCard);
84
+ }
85
+ });
86
+ // A2A Protocol Handler (supports both agent-level and graph-level)
87
+ app.post('/a2a', async (c) => {
88
+ const otelHeaders = {
89
+ traceparent: c.req.header('traceparent'),
90
+ tracestate: c.req.header('tracestate'),
91
+ baggage: c.req.header('baggage'),
92
+ };
93
+ logger.info({
94
+ otelHeaders,
95
+ path: c.req.path,
96
+ method: c.req.method,
97
+ }, 'OpenTelemetry headers: a2a');
98
+ // Get execution context from API key authentication
99
+ const executionContext = getRequestExecutionContext(c);
100
+ const { tenantId, projectId, graphId, agentId } = executionContext;
101
+ // If agentId is defined in execution context, run agent-level logic
102
+ if (agentId) {
103
+ logger.info({
104
+ message: 'a2a (agent-level)',
105
+ tenantId,
106
+ projectId,
107
+ graphId,
108
+ agentId,
109
+ }, 'agent-level a2a endpoint');
110
+ // Ensure agent is registered (lazy loading)
111
+ const agent = await getRegisteredAgent(executionContext);
112
+ if (!agent) {
113
+ return c.json({
114
+ jsonrpc: '2.0',
115
+ error: { code: -32004, message: 'Agent not found' },
116
+ id: null,
117
+ }, 404);
118
+ }
119
+ return a2aHandler(c, agent);
120
+ }
121
+ else {
122
+ // Run graph-level logic
123
+ logger.info({
124
+ message: 'a2a (graph-level)',
125
+ tenantId,
126
+ projectId,
127
+ graphId,
128
+ }, 'graph-level a2a endpoint');
129
+ // fetch the graph and the default agent
130
+ const graph = await getAgentGraphWithDefaultAgent(dbClient)({
131
+ scopes: { tenantId, projectId },
132
+ graphId,
133
+ });
134
+ if (!graph) {
135
+ return c.json({
136
+ jsonrpc: '2.0',
137
+ error: { code: -32004, message: 'Agent not found' },
138
+ id: null,
139
+ }, 404);
140
+ }
141
+ executionContext.agentId = graph.defaultAgentId;
142
+ // fetch the default agent and use it as entry point for the graph
143
+ const defaultAgent = await getRegisteredAgent(executionContext);
144
+ if (!defaultAgent) {
145
+ return c.json({
146
+ jsonrpc: '2.0',
147
+ error: { code: -32004, message: 'Agent not found' },
148
+ id: null,
149
+ }, 404);
150
+ }
151
+ // Use the existing a2aHandler with the default agent as a registered agent
152
+ return a2aHandler(c, defaultAgent);
153
+ }
154
+ });
155
+ export default app;
@@ -0,0 +1,4 @@
1
+ import { OpenAPIHono } from '@hono/zod-openapi';
2
+ declare const app: OpenAPIHono<import("hono").Env, {}, "/">;
3
+ export default app;
4
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/routes/chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAyB7D,QAAA,MAAM,GAAG,0CAAoB,CAAC;AAyW9B,eAAe,GAAG,CAAC"}
@@ -0,0 +1,308 @@
1
+ import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
2
+ // import { Hono } from 'hono';
3
+ import { trace } from '@opentelemetry/api';
4
+ import { streamSSE } from 'hono/streaming';
5
+ import { nanoid } from 'nanoid';
6
+ import { z } from 'zod';
7
+ import { handleContextResolution } from '@inkeep/agents-core';
8
+ import { createMessage, createOrGetConversation, getAgentById, getAgentGraphWithDefaultAgent, getActiveAgentForConversation, setActiveAgentForConversation, getFullGraph, getRequestExecutionContext, } from '@inkeep/agents-core';
9
+ import { ExecutionHandler } from '../handlers/executionHandler.js';
10
+ import { getLogger } from '../logger.js';
11
+ import { contextValidationMiddleware } from '@inkeep/agents-core';
12
+ import { createSSEStreamHelper } from '../utils/stream-helpers.js';
13
+ import dbClient from '../data/db/dbClient.js';
14
+ const app = new OpenAPIHono();
15
+ const logger = getLogger('completionsHandler');
16
+ // Define the OpenAPI route schema
17
+ const chatCompletionsRoute = createRoute({
18
+ method: 'post',
19
+ path: '/completions',
20
+ tags: ['chat'],
21
+ summary: 'Create chat completion',
22
+ description: 'Creates a new chat completion with streaming SSE response using the configured agent graph',
23
+ security: [{ bearerAuth: [] }],
24
+ request: {
25
+ body: {
26
+ content: {
27
+ 'application/json': {
28
+ schema: z.object({
29
+ model: z.string().describe('The model to use for the completion'),
30
+ messages: z
31
+ .array(z.object({
32
+ role: z
33
+ .enum(['system', 'user', 'assistant', 'function', 'tool'])
34
+ .describe('The role of the message'),
35
+ content: z
36
+ .union([
37
+ z.string(),
38
+ z.array(z.strictObject({
39
+ type: z.string(),
40
+ text: z.string().optional(),
41
+ })),
42
+ ])
43
+ .describe('The message content'),
44
+ name: z.string().optional().describe('The name of the message sender'),
45
+ }))
46
+ .describe('The conversation messages'),
47
+ temperature: z.number().optional().describe('Controls randomness (0-1)'),
48
+ top_p: z.number().optional().describe('Controls nucleus sampling'),
49
+ n: z.number().optional().describe('Number of completions to generate'),
50
+ stream: z.boolean().optional().describe('Whether to stream the response'),
51
+ max_tokens: z.number().optional().describe('Maximum tokens to generate'),
52
+ presence_penalty: z.number().optional().describe('Presence penalty (-2 to 2)'),
53
+ frequency_penalty: z.number().optional().describe('Frequency penalty (-2 to 2)'),
54
+ logit_bias: z.record(z.string(), z.number()).optional().describe('Token logit bias'),
55
+ user: z.string().optional().describe('User identifier'),
56
+ conversationId: z.string().optional().describe('Conversation ID for multi-turn chat'),
57
+ tools: z.array(z.string()).optional().describe('Available tools'),
58
+ runConfig: z.record(z.string(), z.unknown()).optional().describe('Run configuration'),
59
+ requestContext: z
60
+ .record(z.string(), z.unknown())
61
+ .optional()
62
+ .describe('Context data for template processing (validated against context config schema)'),
63
+ }),
64
+ },
65
+ },
66
+ },
67
+ },
68
+ responses: {
69
+ 200: {
70
+ description: 'Streaming chat completion response in Server-Sent Events format',
71
+ headers: z.object({
72
+ 'Content-Type': z.string().default('text/event-stream'),
73
+ 'Cache-Control': z.string().default('no-cache'),
74
+ Connection: z.string().default('keep-alive'),
75
+ }),
76
+ content: {
77
+ 'text/event-stream': {
78
+ schema: z.string().describe('Server-Sent Events stream with chat completion chunks'),
79
+ },
80
+ },
81
+ },
82
+ 400: {
83
+ description: 'Invalid request context or parameters',
84
+ content: {
85
+ 'application/json': {
86
+ schema: z.object({
87
+ error: z.string(),
88
+ details: z
89
+ .array(z.object({
90
+ field: z.string(),
91
+ message: z.string(),
92
+ value: z.unknown().optional(),
93
+ }))
94
+ .optional(),
95
+ }),
96
+ },
97
+ },
98
+ },
99
+ 404: {
100
+ description: 'Agent graph or agent not found',
101
+ content: {
102
+ 'application/json': {
103
+ schema: z.object({
104
+ error: z.string(),
105
+ }),
106
+ },
107
+ },
108
+ },
109
+ 500: {
110
+ description: 'Internal server error',
111
+ content: {
112
+ 'application/json': {
113
+ schema: z.object({
114
+ error: z.string(),
115
+ message: z.string(),
116
+ }),
117
+ },
118
+ },
119
+ },
120
+ },
121
+ });
122
+ // Apply context validation middleware
123
+ app.use('/completions', contextValidationMiddleware(dbClient));
124
+ app.openapi(chatCompletionsRoute, async (c) => {
125
+ getLogger('chat').info({
126
+ path: c.req.path,
127
+ method: c.req.method,
128
+ params: c.req.param(),
129
+ }, 'Chat route accessed');
130
+ const otelHeaders = {
131
+ traceparent: c.req.header('traceparent'),
132
+ tracestate: c.req.header('tracestate'),
133
+ baggage: c.req.header('baggage'),
134
+ };
135
+ logger.info({
136
+ otelHeaders,
137
+ path: c.req.path,
138
+ method: c.req.method,
139
+ }, 'OpenTelemetry headers: chat');
140
+ try {
141
+ // Get execution context from API key authentication
142
+ const executionContext = getRequestExecutionContext(c);
143
+ const { tenantId, projectId, graphId } = executionContext;
144
+ getLogger('chat').debug({
145
+ tenantId,
146
+ graphId,
147
+ }, 'Extracted chat parameters from API key context');
148
+ // Get conversationId from request body or generate new one
149
+ const body = c.req.valid('json');
150
+ const conversationId = body.conversationId || nanoid();
151
+ // Get the graph from the full graph system first, fall back to legacy system
152
+ const fullGraph = await getFullGraph(dbClient)({
153
+ scopes: { tenantId, projectId },
154
+ graphId,
155
+ });
156
+ let agentGraph;
157
+ let defaultAgentId;
158
+ if (fullGraph) {
159
+ // Use full graph system
160
+ agentGraph = {
161
+ id: fullGraph.id,
162
+ name: fullGraph.name,
163
+ tenantId,
164
+ projectId,
165
+ defaultAgentId: fullGraph.defaultAgentId,
166
+ };
167
+ const agentKeys = Object.keys(fullGraph.agents || {});
168
+ const firstAgentId = agentKeys.length > 0 ? agentKeys[0] : '';
169
+ defaultAgentId = fullGraph.defaultAgentId || firstAgentId; // Use first agent if no defaultAgentId
170
+ }
171
+ else {
172
+ // Fall back to legacy system
173
+ agentGraph = await getAgentGraphWithDefaultAgent(dbClient)({
174
+ scopes: { tenantId, projectId },
175
+ graphId,
176
+ });
177
+ if (!agentGraph) {
178
+ return c.json({ error: 'Agent graph not found' }, 404);
179
+ }
180
+ defaultAgentId = agentGraph.defaultAgentId || '';
181
+ }
182
+ if (!defaultAgentId) {
183
+ return c.json({ error: 'No default agent found in graph' }, 404);
184
+ }
185
+ // Get or create conversation with the default agent
186
+ await createOrGetConversation(dbClient)({
187
+ tenantId,
188
+ projectId,
189
+ id: conversationId,
190
+ activeAgentId: defaultAgentId,
191
+ });
192
+ const activeAgent = await getActiveAgentForConversation(dbClient)({
193
+ scopes: { tenantId, projectId },
194
+ conversationId,
195
+ });
196
+ if (!activeAgent) {
197
+ // Use the default agent from the graph instead of headAgentId
198
+ setActiveAgentForConversation(dbClient)({
199
+ scopes: { tenantId, projectId },
200
+ conversationId,
201
+ agentId: defaultAgentId,
202
+ });
203
+ }
204
+ const agentId = activeAgent?.activeAgentId || defaultAgentId;
205
+ const agentInfo = await getAgentById(dbClient)({
206
+ scopes: { tenantId, projectId },
207
+ agentId: agentId,
208
+ });
209
+ if (!agentInfo) {
210
+ return c.json({ error: 'Agent not found' }, 404);
211
+ }
212
+ // Get validated context from middleware (falls back to body.context if no validation)
213
+ const validatedContext = c.get('validatedContext') || body.requestContext || {};
214
+ // Context resolution with intelligent conversation state detection
215
+ await handleContextResolution(tenantId, projectId, conversationId, graphId, validatedContext, dbClient);
216
+ logger.info({
217
+ tenantId,
218
+ graphId,
219
+ conversationId,
220
+ defaultAgentId,
221
+ activeAgentId: activeAgent?.activeAgentId || 'none',
222
+ hasContextConfig: !!agentGraph.contextConfigId,
223
+ hasRequestContext: !!body.requestContext,
224
+ hasValidatedContext: !!validatedContext,
225
+ validatedContextKeys: Object.keys(validatedContext),
226
+ }, 'parameters');
227
+ const requestId = `chatcmpl-${Date.now()}`;
228
+ const timestamp = Math.floor(Date.now() / 1000);
229
+ // Extract user message for context
230
+ const lastUserMessage = body.messages
231
+ .filter((msg) => msg.role === 'user')
232
+ .slice(-1)[0];
233
+ const userMessage = lastUserMessage ? getMessageText(lastUserMessage.content) : '';
234
+ const messageSpan = trace.getActiveSpan();
235
+ if (messageSpan) {
236
+ messageSpan.setAttributes({
237
+ 'message.content': userMessage,
238
+ 'message.timestamp': Date.now(),
239
+ });
240
+ }
241
+ // Store the user message in the database
242
+ await createMessage(dbClient)({
243
+ id: nanoid(),
244
+ tenantId,
245
+ projectId,
246
+ conversationId,
247
+ role: 'user',
248
+ content: {
249
+ text: userMessage,
250
+ },
251
+ visibility: 'user-facing',
252
+ messageType: 'chat',
253
+ });
254
+ if (messageSpan) {
255
+ messageSpan.addEvent('user.message.stored', {
256
+ 'message.id': conversationId,
257
+ 'database.operation': 'insert',
258
+ });
259
+ }
260
+ // Use Hono's streamSSE helper for proper SSE formatting
261
+ return streamSSE(c, async (stream) => {
262
+ // Create SSE stream helper
263
+ const sseHelper = createSSEStreamHelper(stream, requestId, timestamp);
264
+ // Start with the role
265
+ await sseHelper.writeRole();
266
+ logger.info({ agentId }, 'Starting execution');
267
+ // Use the execution handler
268
+ const executionHandler = new ExecutionHandler();
269
+ const result = await executionHandler.execute({
270
+ executionContext,
271
+ conversationId,
272
+ userMessage,
273
+ initialAgentId: agentId,
274
+ requestId,
275
+ sseHelper,
276
+ });
277
+ logger.info({ result }, `Execution completed: ${result.success ? 'success' : 'failed'} after ${result.iterations} iterations`);
278
+ if (!result.success) {
279
+ // If execution failed and no error was already streamed, send a default error
280
+ await sseHelper.writeError('Sorry, I was unable to process your request at this time. Please try again.');
281
+ }
282
+ // Complete the stream
283
+ await sseHelper.complete();
284
+ });
285
+ }
286
+ catch (error) {
287
+ console.error('❌ Error in chat completions endpoint:', {
288
+ error: error instanceof Error ? error.message : error,
289
+ stack: error instanceof Error ? error.stack : undefined,
290
+ });
291
+ return c.json({
292
+ error: 'Failed to process chat completion',
293
+ message: error instanceof Error ? error.message : 'Unknown error',
294
+ }, 500);
295
+ }
296
+ });
297
+ // Helper function to extract text from content
298
+ const getMessageText = (content) => {
299
+ if (typeof content === 'string') {
300
+ return content;
301
+ }
302
+ // For content arrays, extract text from all text items
303
+ return content
304
+ .filter((item) => item.type === 'text' && item.text)
305
+ .map((item) => item.text)
306
+ .join(' ');
307
+ };
308
+ export default app;
@@ -0,0 +1,4 @@
1
+ import { OpenAPIHono } from '@hono/zod-openapi';
2
+ declare const app: OpenAPIHono<import("hono").Env, {}, "/">;
3
+ export default app;
4
+ //# sourceMappingURL=chatDataStream.d.ts.map