@inkeep/agents-run-api 0.0.0-dev-20250910232631
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/LICENSE.md +49 -0
- package/README.md +117 -0
- package/dist/__tests__/setup.d.ts +4 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +80 -0
- package/dist/__tests__/utils/testProject.d.ts +18 -0
- package/dist/__tests__/utils/testProject.d.ts.map +1 -0
- package/dist/__tests__/utils/testProject.js +26 -0
- package/dist/__tests__/utils/testRequest.d.ts +8 -0
- package/dist/__tests__/utils/testRequest.d.ts.map +1 -0
- package/dist/__tests__/utils/testRequest.js +32 -0
- package/dist/__tests__/utils/testTenant.d.ts +64 -0
- package/dist/__tests__/utils/testTenant.d.ts.map +1 -0
- package/dist/__tests__/utils/testTenant.js +71 -0
- package/dist/a2a/client.d.ts +182 -0
- package/dist/a2a/client.d.ts.map +1 -0
- package/dist/a2a/client.js +645 -0
- package/dist/a2a/handlers.d.ts +4 -0
- package/dist/a2a/handlers.d.ts.map +1 -0
- package/dist/a2a/handlers.js +656 -0
- package/dist/a2a/transfer.d.ts +18 -0
- package/dist/a2a/transfer.d.ts.map +1 -0
- package/dist/a2a/transfer.js +22 -0
- package/dist/a2a/types.d.ts +63 -0
- package/dist/a2a/types.d.ts.map +1 -0
- package/dist/a2a/types.js +1 -0
- package/dist/agents/Agent.d.ts +151 -0
- package/dist/agents/Agent.d.ts.map +1 -0
- package/dist/agents/Agent.js +1164 -0
- package/dist/agents/ModelFactory.d.ts +62 -0
- package/dist/agents/ModelFactory.d.ts.map +1 -0
- package/dist/agents/ModelFactory.js +208 -0
- package/dist/agents/SystemPromptBuilder.d.ts +14 -0
- package/dist/agents/SystemPromptBuilder.d.ts.map +1 -0
- package/dist/agents/SystemPromptBuilder.js +62 -0
- package/dist/agents/ToolSessionManager.d.ts +53 -0
- package/dist/agents/ToolSessionManager.d.ts.map +1 -0
- package/dist/agents/ToolSessionManager.js +106 -0
- package/dist/agents/artifactTools.d.ts +30 -0
- package/dist/agents/artifactTools.d.ts.map +1 -0
- package/dist/agents/artifactTools.js +463 -0
- package/dist/agents/generateTaskHandler.d.ts +41 -0
- package/dist/agents/generateTaskHandler.d.ts.map +1 -0
- package/dist/agents/generateTaskHandler.js +350 -0
- package/dist/agents/relationTools.d.ts +35 -0
- package/dist/agents/relationTools.d.ts.map +1 -0
- package/dist/agents/relationTools.js +246 -0
- package/dist/agents/types.d.ts +23 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/versions/V1Config.d.ts +21 -0
- package/dist/agents/versions/V1Config.d.ts.map +1 -0
- package/dist/agents/versions/V1Config.js +285 -0
- package/dist/app.d.ts +5 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +219 -0
- package/dist/data/agentGraph.d.ts +4 -0
- package/dist/data/agentGraph.d.ts.map +1 -0
- package/dist/data/agentGraph.js +73 -0
- package/dist/data/agents.d.ts +4 -0
- package/dist/data/agents.d.ts.map +1 -0
- package/dist/data/agents.js +78 -0
- package/dist/data/conversations.d.ts +59 -0
- package/dist/data/conversations.d.ts.map +1 -0
- package/dist/data/conversations.js +216 -0
- package/dist/data/db/clean.d.ts +6 -0
- package/dist/data/db/clean.d.ts.map +1 -0
- package/dist/data/db/clean.js +77 -0
- package/dist/data/db/dbClient.d.ts +3 -0
- package/dist/data/db/dbClient.d.ts.map +1 -0
- package/dist/data/db/dbClient.js +13 -0
- package/dist/env.d.ts +45 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +64 -0
- package/dist/handlers/executionHandler.d.ts +36 -0
- package/dist/handlers/executionHandler.d.ts.map +1 -0
- package/dist/handlers/executionHandler.js +415 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/instrumentation.d.ts +13 -0
- package/dist/instrumentation.d.ts.map +1 -0
- package/dist/instrumentation.js +66 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +32 -0
- package/dist/middleware/api-key-auth.d.ts +22 -0
- package/dist/middleware/api-key-auth.d.ts.map +1 -0
- package/dist/middleware/api-key-auth.js +139 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/openapi.d.ts +2 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +36 -0
- package/dist/routes/agents.d.ts +10 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +158 -0
- package/dist/routes/chat.d.ts +10 -0
- package/dist/routes/chat.d.ts.map +1 -0
- package/dist/routes/chat.js +307 -0
- package/dist/routes/chatDataStream.d.ts +10 -0
- package/dist/routes/chatDataStream.d.ts.map +1 -0
- package/dist/routes/chatDataStream.js +185 -0
- package/dist/routes/mcp.d.ts +10 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +500 -0
- package/dist/tracer.d.ts +24 -0
- package/dist/tracer.d.ts.map +1 -0
- package/dist/tracer.js +107 -0
- package/dist/types/chat.d.ts +25 -0
- package/dist/types/chat.d.ts.map +1 -0
- package/dist/types/chat.js +1 -0
- package/dist/types/execution-context.d.ts +14 -0
- package/dist/types/execution-context.d.ts.map +1 -0
- package/dist/types/execution-context.js +14 -0
- package/dist/utils/agent-operations.d.ts +93 -0
- package/dist/utils/agent-operations.d.ts.map +1 -0
- package/dist/utils/agent-operations.js +78 -0
- package/dist/utils/artifact-component-schema.d.ts +29 -0
- package/dist/utils/artifact-component-schema.d.ts.map +1 -0
- package/dist/utils/artifact-component-schema.js +119 -0
- package/dist/utils/artifact-parser.d.ts +71 -0
- package/dist/utils/artifact-parser.d.ts.map +1 -0
- package/dist/utils/artifact-parser.js +253 -0
- package/dist/utils/cleanup.d.ts +19 -0
- package/dist/utils/cleanup.d.ts.map +1 -0
- package/dist/utils/cleanup.js +66 -0
- package/dist/utils/data-component-schema.d.ts +6 -0
- package/dist/utils/data-component-schema.d.ts.map +1 -0
- package/dist/utils/data-component-schema.js +43 -0
- package/dist/utils/graph-session.d.ts +230 -0
- package/dist/utils/graph-session.d.ts.map +1 -0
- package/dist/utils/graph-session.js +1199 -0
- package/dist/utils/incremental-stream-parser.d.ts +62 -0
- package/dist/utils/incremental-stream-parser.d.ts.map +1 -0
- package/dist/utils/incremental-stream-parser.js +330 -0
- package/dist/utils/response-formatter.d.ts +26 -0
- package/dist/utils/response-formatter.d.ts.map +1 -0
- package/dist/utils/response-formatter.js +158 -0
- package/dist/utils/stream-helpers.d.ts +186 -0
- package/dist/utils/stream-helpers.d.ts.map +1 -0
- package/dist/utils/stream-helpers.js +603 -0
- package/dist/utils/stream-registry.d.ts +18 -0
- package/dist/utils/stream-registry.d.ts.map +1 -0
- package/dist/utils/stream-registry.js +33 -0
- package/package.json +95 -0
- package/templates/v1/artifact.xml +7 -0
- package/templates/v1/data-component.xml +9 -0
- package/templates/v1/system-prompt.xml +52 -0
- package/templates/v1/thinking-preparation.xml +34 -0
- package/templates/v1/tool.xml +12 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type StreamPart } from './artifact-parser';
|
|
2
|
+
import type { StreamHelper } from './stream-helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Incremental parser that processes streaming text and formats artifacts/objects as they become complete
|
|
5
|
+
* Uses the unified ArtifactParser to eliminate redundancy
|
|
6
|
+
*/
|
|
7
|
+
export declare class IncrementalStreamParser {
|
|
8
|
+
private buffer;
|
|
9
|
+
private pendingTextBuffer;
|
|
10
|
+
private streamHelper;
|
|
11
|
+
private artifactParser;
|
|
12
|
+
private hasStartedRole;
|
|
13
|
+
private collectedParts;
|
|
14
|
+
private contextId;
|
|
15
|
+
private lastChunkWasToolResult;
|
|
16
|
+
constructor(streamHelper: StreamHelper, tenantId: string, contextId: string);
|
|
17
|
+
/**
|
|
18
|
+
* Mark that a tool result just completed, so next text should have spacing
|
|
19
|
+
*/
|
|
20
|
+
markToolResult(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Process a new text chunk for text streaming (handles artifact markers)
|
|
23
|
+
*/
|
|
24
|
+
processTextChunk(chunk: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Process a new object chunk for object streaming (handles JSON objects with artifact references)
|
|
27
|
+
*/
|
|
28
|
+
processObjectChunk(chunk: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Process tool call stream for structured output, streaming components as they complete
|
|
31
|
+
*/
|
|
32
|
+
processToolCallStream(stream: AsyncIterable<any>, targetToolName: string): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Legacy method for backward compatibility - defaults to text processing
|
|
35
|
+
*/
|
|
36
|
+
processChunk(chunk: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Process any remaining buffer content at the end of stream
|
|
39
|
+
*/
|
|
40
|
+
finalize(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Get all collected parts for building the final response
|
|
43
|
+
*/
|
|
44
|
+
getCollectedParts(): StreamPart[];
|
|
45
|
+
/**
|
|
46
|
+
* Parse buffer for complete artifacts and text parts (for text streaming)
|
|
47
|
+
*/
|
|
48
|
+
private parseTextBuffer;
|
|
49
|
+
/**
|
|
50
|
+
* Parse buffer for complete JSON objects with artifact references (for object streaming)
|
|
51
|
+
*/
|
|
52
|
+
private parseObjectBuffer;
|
|
53
|
+
/**
|
|
54
|
+
* Check if text might be the start of an artifact marker
|
|
55
|
+
*/
|
|
56
|
+
private mightBeArtifactStart;
|
|
57
|
+
/**
|
|
58
|
+
* Stream a formatted part (text or data) with smart buffering
|
|
59
|
+
*/
|
|
60
|
+
private streamPart;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=incremental-stream-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental-stream-parser.d.ts","sourceRoot":"","sources":["../../src/utils/incremental-stream-parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AASrD;;;GAGG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,cAAc,CAAoB;IAC1C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,sBAAsB,CAAS;gBAE3B,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAM3E;;OAEG;IACH,cAAc,IAAI,IAAI;IAItB;;OAEG;IACG,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBpD;;OAEG;IACG,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IActD;;OAEG;IACG,qBAAqB,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8G9F;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC/B;;OAEG;IACH,iBAAiB,IAAI,UAAU,EAAE;IAIjC;;OAEG;YACW,eAAe;IAoD7B;;OAEG;YACW,iBAAiB;IA4B/B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;OAEG;YACW,UAAU;CAqDzB"}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { getLogger } from '../logger';
|
|
2
|
+
import { ArtifactParser } from './artifact-parser';
|
|
3
|
+
const logger = getLogger('IncrementalStreamParser');
|
|
4
|
+
/**
|
|
5
|
+
* Incremental parser that processes streaming text and formats artifacts/objects as they become complete
|
|
6
|
+
* Uses the unified ArtifactParser to eliminate redundancy
|
|
7
|
+
*/
|
|
8
|
+
export class IncrementalStreamParser {
|
|
9
|
+
buffer = '';
|
|
10
|
+
pendingTextBuffer = '';
|
|
11
|
+
streamHelper;
|
|
12
|
+
artifactParser;
|
|
13
|
+
hasStartedRole = false;
|
|
14
|
+
collectedParts = [];
|
|
15
|
+
contextId;
|
|
16
|
+
lastChunkWasToolResult = false;
|
|
17
|
+
constructor(streamHelper, tenantId, contextId) {
|
|
18
|
+
this.streamHelper = streamHelper;
|
|
19
|
+
this.contextId = contextId;
|
|
20
|
+
this.artifactParser = new ArtifactParser(tenantId);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Mark that a tool result just completed, so next text should have spacing
|
|
24
|
+
*/
|
|
25
|
+
markToolResult() {
|
|
26
|
+
this.lastChunkWasToolResult = true;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Process a new text chunk for text streaming (handles artifact markers)
|
|
30
|
+
*/
|
|
31
|
+
async processTextChunk(chunk) {
|
|
32
|
+
// If this text follows a tool result and we haven't added any text yet, add spacing
|
|
33
|
+
if (this.lastChunkWasToolResult && this.buffer === '' && chunk.trim()) {
|
|
34
|
+
chunk = '\n\n' + chunk;
|
|
35
|
+
this.lastChunkWasToolResult = false;
|
|
36
|
+
}
|
|
37
|
+
this.buffer += chunk;
|
|
38
|
+
const parseResult = await this.parseTextBuffer();
|
|
39
|
+
// Stream complete parts
|
|
40
|
+
for (const part of parseResult.completeParts) {
|
|
41
|
+
await this.streamPart(part);
|
|
42
|
+
}
|
|
43
|
+
// Update buffer with remaining content
|
|
44
|
+
this.buffer = parseResult.remainingBuffer;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Process a new object chunk for object streaming (handles JSON objects with artifact references)
|
|
48
|
+
*/
|
|
49
|
+
async processObjectChunk(chunk) {
|
|
50
|
+
this.buffer += chunk;
|
|
51
|
+
const parseResult = await this.parseObjectBuffer();
|
|
52
|
+
// Stream complete parts
|
|
53
|
+
for (const part of parseResult.completeParts) {
|
|
54
|
+
await this.streamPart(part);
|
|
55
|
+
}
|
|
56
|
+
// Update buffer with remaining content
|
|
57
|
+
this.buffer = parseResult.remainingBuffer;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Process tool call stream for structured output, streaming components as they complete
|
|
61
|
+
*/
|
|
62
|
+
async processToolCallStream(stream, targetToolName) {
|
|
63
|
+
let jsonBuffer = '';
|
|
64
|
+
let componentBuffer = '';
|
|
65
|
+
let depth = 0;
|
|
66
|
+
let _inDataComponents = false;
|
|
67
|
+
let componentsStreamed = 0;
|
|
68
|
+
const MAX_BUFFER_SIZE = 5 * 1024 * 1024; // 5MB max buffer size
|
|
69
|
+
for await (const part of stream) {
|
|
70
|
+
// Look for tool call deltas with incremental JSON
|
|
71
|
+
if (part.type === 'tool-call-delta' && part.toolName === targetToolName) {
|
|
72
|
+
const delta = part.argsTextDelta || '';
|
|
73
|
+
// Prevent JSON buffer from growing too large
|
|
74
|
+
if (jsonBuffer.length + delta.length > MAX_BUFFER_SIZE) {
|
|
75
|
+
logger.warn('JSON buffer exceeded maximum size, truncating');
|
|
76
|
+
jsonBuffer = jsonBuffer.slice(-MAX_BUFFER_SIZE / 2); // Keep last half
|
|
77
|
+
}
|
|
78
|
+
jsonBuffer += delta;
|
|
79
|
+
// Parse character by character to detect complete components
|
|
80
|
+
for (const char of delta) {
|
|
81
|
+
// Prevent component buffer from growing too large
|
|
82
|
+
if (componentBuffer.length > MAX_BUFFER_SIZE) {
|
|
83
|
+
logger.warn('Component buffer exceeded maximum size, resetting');
|
|
84
|
+
componentBuffer = '';
|
|
85
|
+
depth = 0; // Reset depth tracking
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
componentBuffer += char;
|
|
89
|
+
// Track JSON depth
|
|
90
|
+
if (char === '{') {
|
|
91
|
+
depth++;
|
|
92
|
+
}
|
|
93
|
+
else if (char === '}') {
|
|
94
|
+
depth--;
|
|
95
|
+
// At depth 2, we're inside the dataComponents array
|
|
96
|
+
// When we return to depth 2, we have a complete component
|
|
97
|
+
if (depth === 2 && componentBuffer.includes('"id"')) {
|
|
98
|
+
// Extract just the component object with size limit
|
|
99
|
+
const componentMatch = componentBuffer.match(/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/);
|
|
100
|
+
if (componentMatch) {
|
|
101
|
+
// Size limit check - prevent parsing extremely large JSON objects
|
|
102
|
+
const MAX_COMPONENT_SIZE = 1024 * 1024; // 1MB limit per component
|
|
103
|
+
if (componentMatch[0].length > MAX_COMPONENT_SIZE) {
|
|
104
|
+
logger.warn({
|
|
105
|
+
size: componentMatch[0].length,
|
|
106
|
+
maxSize: MAX_COMPONENT_SIZE,
|
|
107
|
+
}, 'Component exceeds size limit, skipping');
|
|
108
|
+
componentBuffer = ''; // Reset and skip this component
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const component = JSON.parse(componentMatch[0]);
|
|
113
|
+
// Validate component structure before processing
|
|
114
|
+
if (typeof component !== 'object' || !component.id) {
|
|
115
|
+
logger.warn('Invalid component structure, skipping');
|
|
116
|
+
componentBuffer = '';
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Stream this individual component
|
|
120
|
+
const parts = await this.artifactParser.parseObject({
|
|
121
|
+
dataComponents: [component],
|
|
122
|
+
});
|
|
123
|
+
for (const part of parts) {
|
|
124
|
+
await this.streamPart(part);
|
|
125
|
+
}
|
|
126
|
+
componentsStreamed++;
|
|
127
|
+
componentBuffer = ''; // Reset for next component
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// Not valid JSON yet, keep accumulating
|
|
131
|
+
logger.debug({ error: e }, 'Failed to parse component, continuing to accumulate');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Detect when we enter dataComponents array
|
|
137
|
+
if (componentBuffer.includes('"dataComponents"') && componentBuffer.includes('[')) {
|
|
138
|
+
_inDataComponents = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Alternative: if the SDK provides complete tool calls (not deltas)
|
|
143
|
+
else if (part.type === 'tool-call' && part.toolName === targetToolName) {
|
|
144
|
+
// Process the complete args at once
|
|
145
|
+
if (part.args?.dataComponents) {
|
|
146
|
+
const parts = await this.artifactParser.parseObject(part.args);
|
|
147
|
+
for (const part of parts) {
|
|
148
|
+
await this.streamPart(part);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break; // Tool call complete
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
logger.debug({ componentsStreamed }, 'Finished streaming components');
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Legacy method for backward compatibility - defaults to text processing
|
|
158
|
+
*/
|
|
159
|
+
async processChunk(chunk) {
|
|
160
|
+
await this.processTextChunk(chunk);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Process any remaining buffer content at the end of stream
|
|
164
|
+
*/
|
|
165
|
+
async finalize() {
|
|
166
|
+
if (this.buffer.trim()) {
|
|
167
|
+
// Process remaining buffer as final text
|
|
168
|
+
const part = {
|
|
169
|
+
kind: 'text',
|
|
170
|
+
text: this.buffer.trim(),
|
|
171
|
+
};
|
|
172
|
+
await this.streamPart(part);
|
|
173
|
+
}
|
|
174
|
+
// Flush any remaining buffered text
|
|
175
|
+
if (this.pendingTextBuffer.trim()) {
|
|
176
|
+
// Clean up any artifact-related tags or remnants before final flush
|
|
177
|
+
// Use safe, non-backtracking regex patterns to prevent ReDoS attacks
|
|
178
|
+
const cleanedText = this.pendingTextBuffer
|
|
179
|
+
.replace(/<\/?artifact:ref(?:\s[^>]*)?>\/?>/g, '') // Remove artifact:ref tags safely
|
|
180
|
+
.replace(/<\/?artifact(?:\s[^>]*)?>\/?>/g, '') // Remove artifact tags safely
|
|
181
|
+
.replace(/<\/(?:\w+:)?artifact>/g, '') // Remove closing artifact tags safely
|
|
182
|
+
.trim();
|
|
183
|
+
if (cleanedText) {
|
|
184
|
+
this.collectedParts.push({
|
|
185
|
+
kind: 'text',
|
|
186
|
+
text: cleanedText,
|
|
187
|
+
});
|
|
188
|
+
await this.streamHelper.streamText(cleanedText, 50);
|
|
189
|
+
}
|
|
190
|
+
this.pendingTextBuffer = '';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get all collected parts for building the final response
|
|
195
|
+
*/
|
|
196
|
+
getCollectedParts() {
|
|
197
|
+
return [...this.collectedParts];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Parse buffer for complete artifacts and text parts (for text streaming)
|
|
201
|
+
*/
|
|
202
|
+
async parseTextBuffer() {
|
|
203
|
+
const completeParts = [];
|
|
204
|
+
const workingBuffer = this.buffer;
|
|
205
|
+
// Check if we have incomplete artifact markers
|
|
206
|
+
if (this.artifactParser.hasIncompleteArtifact(workingBuffer)) {
|
|
207
|
+
// Find safe boundary to stream text before incomplete artifact
|
|
208
|
+
const safeEnd = this.artifactParser.findSafeTextBoundary(workingBuffer);
|
|
209
|
+
if (safeEnd > 0) {
|
|
210
|
+
const safeText = workingBuffer.slice(0, safeEnd);
|
|
211
|
+
// Parse the safe portion for complete artifacts
|
|
212
|
+
const parts = await this.artifactParser.parseText(safeText);
|
|
213
|
+
completeParts.push(...parts);
|
|
214
|
+
return {
|
|
215
|
+
completeParts,
|
|
216
|
+
remainingBuffer: workingBuffer.slice(safeEnd),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// Buffer contains only incomplete artifact
|
|
220
|
+
return {
|
|
221
|
+
completeParts: [],
|
|
222
|
+
remainingBuffer: workingBuffer,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
// No incomplete artifacts, parse the entire buffer
|
|
226
|
+
const parts = await this.artifactParser.parseText(workingBuffer);
|
|
227
|
+
// Check last part - if it's text, it might be incomplete
|
|
228
|
+
if (parts.length > 0 && parts[parts.length - 1].kind === 'text') {
|
|
229
|
+
const lastPart = parts[parts.length - 1];
|
|
230
|
+
const lastText = lastPart.text || '';
|
|
231
|
+
// Keep some text in buffer if it might be start of artifact
|
|
232
|
+
if (this.mightBeArtifactStart(lastText)) {
|
|
233
|
+
parts.pop(); // Remove last text part
|
|
234
|
+
return {
|
|
235
|
+
completeParts: parts,
|
|
236
|
+
remainingBuffer: lastText,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
completeParts: parts,
|
|
242
|
+
remainingBuffer: '',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Parse buffer for complete JSON objects with artifact references (for object streaming)
|
|
247
|
+
*/
|
|
248
|
+
async parseObjectBuffer() {
|
|
249
|
+
const completeParts = [];
|
|
250
|
+
try {
|
|
251
|
+
// Try to parse as complete JSON
|
|
252
|
+
const parsed = JSON.parse(this.buffer);
|
|
253
|
+
const parts = await this.artifactParser.parseObject(parsed);
|
|
254
|
+
return {
|
|
255
|
+
completeParts: parts,
|
|
256
|
+
remainingBuffer: '',
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// JSON is incomplete, try partial parsing
|
|
261
|
+
const { complete, remaining } = this.artifactParser.parsePartialJSON(this.buffer);
|
|
262
|
+
for (const obj of complete) {
|
|
263
|
+
const parts = await this.artifactParser.parseObject(obj);
|
|
264
|
+
completeParts.push(...parts);
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
completeParts,
|
|
268
|
+
remainingBuffer: remaining,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Check if text might be the start of an artifact marker
|
|
274
|
+
*/
|
|
275
|
+
mightBeArtifactStart(text) {
|
|
276
|
+
const lastChars = text.slice(-20); // Check last 20 chars
|
|
277
|
+
return lastChars.includes('<') && !lastChars.includes('/>');
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Stream a formatted part (text or data) with smart buffering
|
|
281
|
+
*/
|
|
282
|
+
async streamPart(part) {
|
|
283
|
+
// Collect for final response
|
|
284
|
+
this.collectedParts.push({ ...part });
|
|
285
|
+
if (!this.hasStartedRole) {
|
|
286
|
+
await this.streamHelper.writeRole('assistant');
|
|
287
|
+
this.hasStartedRole = true;
|
|
288
|
+
}
|
|
289
|
+
if (part.kind === 'text' && part.text) {
|
|
290
|
+
// Add to pending buffer
|
|
291
|
+
this.pendingTextBuffer += part.text;
|
|
292
|
+
// Flush if safe to do so
|
|
293
|
+
if (!this.artifactParser.hasIncompleteArtifact(this.pendingTextBuffer)) {
|
|
294
|
+
// Clean up any artifact-related tags or remnants before flushing
|
|
295
|
+
// Use safe, non-backtracking regex patterns to prevent ReDoS attacks
|
|
296
|
+
const cleanedText = this.pendingTextBuffer
|
|
297
|
+
.replace(/<\/?artifact:ref(?:\s[^>]*)?>\/?>/g, '') // Remove artifact:ref tags safely
|
|
298
|
+
.replace(/<\/?artifact(?:\s[^>]*)?>\/?>/g, '') // Remove artifact tags safely
|
|
299
|
+
.replace(/<\/(?:\w+:)?artifact>/g, ''); // Remove closing artifact tags safely
|
|
300
|
+
if (cleanedText.trim()) {
|
|
301
|
+
await this.streamHelper.streamText(cleanedText, 50);
|
|
302
|
+
}
|
|
303
|
+
this.pendingTextBuffer = '';
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else if (part.kind === 'data' && part.data) {
|
|
307
|
+
// Flush any pending text before streaming data
|
|
308
|
+
if (this.pendingTextBuffer) {
|
|
309
|
+
// Clean up any artifact-related tags or remnants before flushing
|
|
310
|
+
// Use safe, non-backtracking regex patterns to prevent ReDoS attacks
|
|
311
|
+
const cleanedText = this.pendingTextBuffer
|
|
312
|
+
.replace(/<\/?artifact:ref(?:\s[^>]*)?>\/?>/g, '') // Remove artifact:ref tags safely
|
|
313
|
+
.replace(/<\/?artifact(?:\s[^>]*)?>\/?>/g, '') // Remove artifact tags safely
|
|
314
|
+
.replace(/<\/(?:\w+:)?artifact>/g, ''); // Remove closing artifact tags safely
|
|
315
|
+
if (cleanedText.trim()) {
|
|
316
|
+
await this.streamHelper.streamText(cleanedText, 50);
|
|
317
|
+
}
|
|
318
|
+
this.pendingTextBuffer = '';
|
|
319
|
+
}
|
|
320
|
+
// Determine if this is an artifact or regular data component
|
|
321
|
+
const isArtifact = part.data.artifactId && part.data.taskId;
|
|
322
|
+
if (isArtifact) {
|
|
323
|
+
await this.streamHelper.writeData('data-artifact', part.data);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
await this.streamHelper.writeData('data-component', part.data);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { MessageContent } from '@inkeep/agents-core';
|
|
2
|
+
/**
|
|
3
|
+
* Response formatter that uses the unified ArtifactParser to convert artifact markers
|
|
4
|
+
* into data parts for consistent artifact handling across all agent responses
|
|
5
|
+
*/
|
|
6
|
+
export declare class ResponseFormatter {
|
|
7
|
+
private artifactParser;
|
|
8
|
+
constructor(tenantId: string);
|
|
9
|
+
/**
|
|
10
|
+
* Process structured object response and replace artifact markers with actual artifacts
|
|
11
|
+
*/
|
|
12
|
+
formatObjectResponse(responseObject: any, contextId: string): Promise<MessageContent>;
|
|
13
|
+
/**
|
|
14
|
+
* Process agent response and convert artifact markers to data parts
|
|
15
|
+
*/
|
|
16
|
+
formatResponse(responseText: string, contextId: string): Promise<MessageContent>;
|
|
17
|
+
/**
|
|
18
|
+
* Count unique artifacts in parts array
|
|
19
|
+
*/
|
|
20
|
+
private countUniqueArtifacts;
|
|
21
|
+
/**
|
|
22
|
+
* Log artifacts found in parts to span
|
|
23
|
+
*/
|
|
24
|
+
private logArtifacts;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=response-formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-formatter.d.ts","sourceRoot":"","sources":["../../src/utils/response-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAgB1D;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,cAAc,CAAiB;gBAE3B,QAAQ,EAAE,MAAM;IAI5B;;OAEG;IACG,oBAAoB,CAAC,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA0C3F;;OAEG;IACG,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgEtF;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgB5B;;OAEG;IACH,OAAO,CAAC,YAAY;CAwBrB"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { trace } from '@opentelemetry/api';
|
|
2
|
+
import { getLogger } from '../logger';
|
|
3
|
+
import { SERVICE_VERSION } from '../tracer';
|
|
4
|
+
import { ArtifactParser } from './artifact-parser';
|
|
5
|
+
const logger = getLogger('ResponseFormatter');
|
|
6
|
+
// Service name for response formatter tracer
|
|
7
|
+
const RESPONSE_FORMATTER_SERVICE = 'responseFormatter';
|
|
8
|
+
function getResponseFormatterTracer() {
|
|
9
|
+
const tracerProvider = trace.getTracerProvider();
|
|
10
|
+
return tracerProvider.getTracer(RESPONSE_FORMATTER_SERVICE, SERVICE_VERSION);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Response formatter that uses the unified ArtifactParser to convert artifact markers
|
|
14
|
+
* into data parts for consistent artifact handling across all agent responses
|
|
15
|
+
*/
|
|
16
|
+
export class ResponseFormatter {
|
|
17
|
+
artifactParser;
|
|
18
|
+
constructor(tenantId) {
|
|
19
|
+
this.artifactParser = new ArtifactParser(tenantId);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Process structured object response and replace artifact markers with actual artifacts
|
|
23
|
+
*/
|
|
24
|
+
async formatObjectResponse(responseObject, contextId) {
|
|
25
|
+
const tracer = getResponseFormatterTracer();
|
|
26
|
+
return tracer.startActiveSpan('response.formatObject', async (span) => {
|
|
27
|
+
try {
|
|
28
|
+
// Get all artifacts available in this context
|
|
29
|
+
const artifactMap = await this.artifactParser.getContextArtifacts(contextId);
|
|
30
|
+
span.setAttributes({
|
|
31
|
+
'response.type': 'object',
|
|
32
|
+
'response.availableArtifacts': artifactMap.size,
|
|
33
|
+
});
|
|
34
|
+
// Parse the object using unified parser
|
|
35
|
+
const parts = await this.artifactParser.parseObject(responseObject, artifactMap);
|
|
36
|
+
// Count and log metrics
|
|
37
|
+
const uniqueArtifacts = this.countUniqueArtifacts(parts);
|
|
38
|
+
span.setAttributes({
|
|
39
|
+
'response.dataPartsCount': parts.length,
|
|
40
|
+
'response.artifactsCount': uniqueArtifacts,
|
|
41
|
+
'response.totalPartsCount': parts.length,
|
|
42
|
+
});
|
|
43
|
+
this.logArtifacts(span, parts);
|
|
44
|
+
span.addEvent('response.parts.final_output', {
|
|
45
|
+
final_parts_array: JSON.stringify(parts, null, 2),
|
|
46
|
+
});
|
|
47
|
+
return { parts };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
span.recordException(error);
|
|
51
|
+
logger.error({ error, responseObject }, 'Error formatting object response');
|
|
52
|
+
return {
|
|
53
|
+
parts: [{ kind: 'data', data: responseObject }],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
span.end();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Process agent response and convert artifact markers to data parts
|
|
63
|
+
*/
|
|
64
|
+
async formatResponse(responseText, contextId) {
|
|
65
|
+
const tracer = getResponseFormatterTracer();
|
|
66
|
+
return tracer.startActiveSpan('response.format', async (span) => {
|
|
67
|
+
try {
|
|
68
|
+
span.setAttributes({
|
|
69
|
+
'response.hasArtifactMarkers': this.artifactParser.hasArtifactMarkers(responseText),
|
|
70
|
+
'response.contextId': contextId,
|
|
71
|
+
'response.textLength': responseText.length,
|
|
72
|
+
});
|
|
73
|
+
// Check if the response contains artifact markers
|
|
74
|
+
if (!this.artifactParser.hasArtifactMarkers(responseText)) {
|
|
75
|
+
span.setAttributes({
|
|
76
|
+
'response.result': 'no_markers_found',
|
|
77
|
+
});
|
|
78
|
+
return { parts: [{ kind: 'text', text: responseText }] };
|
|
79
|
+
}
|
|
80
|
+
// Get all artifacts available in this context
|
|
81
|
+
const artifactMap = await this.artifactParser.getContextArtifacts(contextId);
|
|
82
|
+
span.setAttributes({
|
|
83
|
+
'response.type': 'text',
|
|
84
|
+
'response.availableArtifacts': artifactMap.size,
|
|
85
|
+
});
|
|
86
|
+
// Parse text using unified parser
|
|
87
|
+
const parts = await this.artifactParser.parseText(responseText, artifactMap);
|
|
88
|
+
// If only one text part, return as plain text
|
|
89
|
+
if (parts.length === 1 && parts[0].kind === 'text') {
|
|
90
|
+
return { text: parts[0].text };
|
|
91
|
+
}
|
|
92
|
+
// Count and log metrics
|
|
93
|
+
const textParts = parts.filter((p) => p.kind === 'text').length;
|
|
94
|
+
const dataParts = parts.filter((p) => p.kind === 'data').length;
|
|
95
|
+
const uniqueArtifacts = this.countUniqueArtifacts(parts);
|
|
96
|
+
span.setAttributes({
|
|
97
|
+
'response.textPartsCount': textParts,
|
|
98
|
+
'response.dataPartsCount': dataParts,
|
|
99
|
+
'response.artifactsCount': uniqueArtifacts,
|
|
100
|
+
'response.totalPartsCount': parts.length,
|
|
101
|
+
});
|
|
102
|
+
this.logArtifacts(span, parts);
|
|
103
|
+
span.addEvent('response.parts.final_output', {
|
|
104
|
+
final_parts_array: JSON.stringify(parts, null, 2),
|
|
105
|
+
});
|
|
106
|
+
return { parts };
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
span.recordException(error);
|
|
110
|
+
span.setStatus({ code: 2, message: error.message });
|
|
111
|
+
logger.error({ error, responseText }, 'Error formatting response');
|
|
112
|
+
return { text: responseText };
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
span.end();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Count unique artifacts in parts array
|
|
121
|
+
*/
|
|
122
|
+
countUniqueArtifacts(parts) {
|
|
123
|
+
const uniqueKeys = new Set(parts
|
|
124
|
+
.filter((p) => p.kind === 'data')
|
|
125
|
+
.map((p) => {
|
|
126
|
+
const data = p.data;
|
|
127
|
+
if (data?.artifactId && data?.taskId) {
|
|
128
|
+
return `${data.artifactId}:${data.taskId}`;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
})
|
|
132
|
+
.filter((key) => key !== null));
|
|
133
|
+
return uniqueKeys.size;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Log artifacts found in parts to span
|
|
137
|
+
*/
|
|
138
|
+
logArtifacts(span, parts) {
|
|
139
|
+
const artifacts = parts
|
|
140
|
+
.filter((p) => p.kind === 'data')
|
|
141
|
+
.map((p) => {
|
|
142
|
+
const data = p.data;
|
|
143
|
+
return {
|
|
144
|
+
artifactId: data?.artifactId,
|
|
145
|
+
name: data?.name,
|
|
146
|
+
taskId: data?.taskId,
|
|
147
|
+
};
|
|
148
|
+
})
|
|
149
|
+
.filter((art) => art.artifactId && art.taskId);
|
|
150
|
+
if (artifacts.length > 0) {
|
|
151
|
+
const uniqueArtifactsList = Array.from(new Map(artifacts.map((art) => [`${art.artifactId}:${art.taskId}`, art])).values());
|
|
152
|
+
span.addEvent('artifacts.found', {
|
|
153
|
+
'artifacts.count': uniqueArtifactsList.length,
|
|
154
|
+
'artifacts.list': JSON.stringify(uniqueArtifactsList),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|