graphlit-client 1.0.20250610006 → 1.0.20250610008
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/dist/client.js +73 -1
- package/dist/streaming/providers.js +85 -2
- package/package.json +1 -1
package/dist/client.js
CHANGED
@@ -1713,7 +1713,79 @@ class Graphlit {
|
|
1713
1713
|
continue;
|
1714
1714
|
}
|
1715
1715
|
try {
|
1716
|
-
|
1716
|
+
let args;
|
1717
|
+
try {
|
1718
|
+
args = JSON.parse(toolCall.arguments);
|
1719
|
+
}
|
1720
|
+
catch (parseError) {
|
1721
|
+
console.error(`Failed to parse tool arguments for ${toolCall.name}:`);
|
1722
|
+
console.error(`Arguments (${toolCall.arguments.length} chars):`, toolCall.arguments);
|
1723
|
+
console.error(`Parse error:`, parseError);
|
1724
|
+
// Check for common truncation patterns
|
1725
|
+
const lastChars = toolCall.arguments.slice(-20);
|
1726
|
+
let isTruncated = false;
|
1727
|
+
if (!toolCall.arguments.includes('}') || !lastChars.includes('}')) {
|
1728
|
+
console.error(`Possible truncation detected - arguments don't end with '}': ...${lastChars}`);
|
1729
|
+
isTruncated = true;
|
1730
|
+
}
|
1731
|
+
// Try to fix truncated JSON by adding missing closing braces
|
1732
|
+
if (isTruncated) {
|
1733
|
+
let fixedJson = toolCall.arguments.trim();
|
1734
|
+
// Count open braces vs close braces to determine how many we need
|
1735
|
+
const openBraces = (fixedJson.match(/\{/g) || []).length;
|
1736
|
+
const closeBraces = (fixedJson.match(/\}/g) || []).length;
|
1737
|
+
const missingBraces = openBraces - closeBraces;
|
1738
|
+
if (missingBraces > 0) {
|
1739
|
+
// Check if we're mid-value (ends with number or boolean)
|
1740
|
+
if (fixedJson.match(/:\s*\d+$/) || fixedJson.match(/:\s*(true|false)$/)) {
|
1741
|
+
// Complete the current property and close
|
1742
|
+
fixedJson += ', "content": ""'; // Add empty content field
|
1743
|
+
}
|
1744
|
+
// Check if we're after a value but missing comma
|
1745
|
+
else if (fixedJson.match(/"\s*:\s*[^,}\s]+$/)) {
|
1746
|
+
// We have a complete value but no comma, add empty content
|
1747
|
+
fixedJson += ', "content": ""';
|
1748
|
+
}
|
1749
|
+
// Add missing closing quote if the string ends with an unfinished string
|
1750
|
+
else if (fixedJson.endsWith('"') === false && fixedJson.includes('"')) {
|
1751
|
+
const lastQuoteIndex = fixedJson.lastIndexOf('"');
|
1752
|
+
const afterLastQuote = fixedJson.slice(lastQuoteIndex + 1);
|
1753
|
+
if (!afterLastQuote.includes('"')) {
|
1754
|
+
fixedJson += '"';
|
1755
|
+
}
|
1756
|
+
}
|
1757
|
+
// Add missing closing braces
|
1758
|
+
fixedJson += '}'.repeat(missingBraces);
|
1759
|
+
console.log(`Attempting to fix truncated JSON by adding ${missingBraces} closing brace(s):`);
|
1760
|
+
console.log(fixedJson);
|
1761
|
+
try {
|
1762
|
+
args = JSON.parse(fixedJson);
|
1763
|
+
console.log(`✅ Successfully fixed truncated JSON for ${toolCall.name}`);
|
1764
|
+
}
|
1765
|
+
catch (fixError) {
|
1766
|
+
console.error(`❌ Failed to fix truncated JSON: ${fixError}`);
|
1767
|
+
// Fall through to error handling below
|
1768
|
+
}
|
1769
|
+
}
|
1770
|
+
}
|
1771
|
+
// If we couldn't parse or fix the JSON, log details and continue
|
1772
|
+
if (!args) {
|
1773
|
+
// Log position mentioned in error if available
|
1774
|
+
const errorMsg = parseError instanceof Error ? parseError.message : '';
|
1775
|
+
const posMatch = errorMsg.match(/position (\d+)/);
|
1776
|
+
if (posMatch) {
|
1777
|
+
const pos = parseInt(posMatch[1]);
|
1778
|
+
const context = toolCall.arguments.slice(Math.max(0, pos - 20), pos + 20);
|
1779
|
+
console.error(`Error context around position ${pos}: ...${context}...`);
|
1780
|
+
}
|
1781
|
+
// Update UI with error - use StreamEvent error type
|
1782
|
+
uiAdapter.handleEvent({
|
1783
|
+
type: "error",
|
1784
|
+
error: `Tool ${toolCall.name} failed: Invalid JSON arguments: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`,
|
1785
|
+
});
|
1786
|
+
continue;
|
1787
|
+
}
|
1788
|
+
}
|
1717
1789
|
// Update UI
|
1718
1790
|
uiAdapter.handleEvent({
|
1719
1791
|
type: "tool_call_start",
|
@@ -1,4 +1,16 @@
|
|
1
1
|
import { getModelName } from "../model-mapping.js";
|
2
|
+
/**
|
3
|
+
* Helper to check if a string is valid JSON
|
4
|
+
*/
|
5
|
+
function isValidJSON(str) {
|
6
|
+
try {
|
7
|
+
JSON.parse(str);
|
8
|
+
return true;
|
9
|
+
}
|
10
|
+
catch {
|
11
|
+
return false;
|
12
|
+
}
|
13
|
+
}
|
2
14
|
/**
|
3
15
|
* Stream with OpenAI SDK
|
4
16
|
*/
|
@@ -115,7 +127,7 @@ onEvent, onComplete) {
|
|
115
127
|
stream: true,
|
116
128
|
temperature: specification.anthropic?.temperature,
|
117
129
|
//top_p: specification.anthropic?.probability,
|
118
|
-
max_tokens: specification.anthropic?.completionTokenLimit ||
|
130
|
+
max_tokens: specification.anthropic?.completionTokenLimit || 8192, // required
|
119
131
|
};
|
120
132
|
if (systemPrompt) {
|
121
133
|
streamConfig.system = systemPrompt;
|
@@ -129,8 +141,14 @@ onEvent, onComplete) {
|
|
129
141
|
}));
|
130
142
|
}
|
131
143
|
const stream = await anthropicClient.messages.create(streamConfig);
|
144
|
+
let activeContentBlock = false;
|
132
145
|
for await (const chunk of stream) {
|
146
|
+
// Debug log all chunk types
|
147
|
+
if (process.env.DEBUG_STREAMING) {
|
148
|
+
console.log(`[Anthropic] Received chunk type: ${chunk.type}`);
|
149
|
+
}
|
133
150
|
if (chunk.type === "content_block_start") {
|
151
|
+
activeContentBlock = true;
|
134
152
|
if (chunk.content_block.type === "tool_use") {
|
135
153
|
const toolCall = {
|
136
154
|
id: chunk.content_block.id,
|
@@ -160,6 +178,11 @@ onEvent, onComplete) {
|
|
160
178
|
const currentTool = toolCalls[toolCalls.length - 1];
|
161
179
|
if (currentTool) {
|
162
180
|
currentTool.arguments += chunk.delta.partial_json;
|
181
|
+
// Debug logging for partial JSON accumulation
|
182
|
+
if (process.env.DEBUG_STREAMING) {
|
183
|
+
console.log(`[Anthropic] Tool ${currentTool.name} - Partial JSON chunk: "${chunk.delta.partial_json}"`);
|
184
|
+
console.log(`[Anthropic] Tool ${currentTool.name} - Total accumulated: ${currentTool.arguments.length} chars`);
|
185
|
+
}
|
163
186
|
onEvent({
|
164
187
|
type: "tool_call_delta",
|
165
188
|
toolCallId: currentTool.id,
|
@@ -169,9 +192,32 @@ onEvent, onComplete) {
|
|
169
192
|
}
|
170
193
|
}
|
171
194
|
else if (chunk.type === "content_block_stop") {
|
195
|
+
activeContentBlock = false;
|
172
196
|
// Tool call complete
|
173
197
|
const currentTool = toolCalls[toolCalls.length - 1];
|
174
198
|
if (currentTool) {
|
199
|
+
// Log the final JSON for debugging
|
200
|
+
if (process.env.DEBUG_STREAMING ||
|
201
|
+
!isValidJSON(currentTool.arguments)) {
|
202
|
+
console.log(`[Anthropic] Tool ${currentTool.name} complete with arguments (${currentTool.arguments.length} chars):`);
|
203
|
+
console.log(currentTool.arguments);
|
204
|
+
// Check if JSON appears truncated
|
205
|
+
const lastChars = currentTool.arguments.slice(-10);
|
206
|
+
if (!lastChars.includes("}") &&
|
207
|
+
currentTool.arguments.length > 100) {
|
208
|
+
console.warn(`[Anthropic] WARNING: JSON may be truncated - doesn't end with '}': ...${lastChars}`);
|
209
|
+
}
|
210
|
+
// Validate JSON
|
211
|
+
try {
|
212
|
+
JSON.parse(currentTool.arguments);
|
213
|
+
if (process.env.DEBUG_STREAMING) {
|
214
|
+
console.log(`[Anthropic] ✅ Valid JSON for ${currentTool.name}`);
|
215
|
+
}
|
216
|
+
}
|
217
|
+
catch (e) {
|
218
|
+
console.error(`[Anthropic] ❌ Invalid JSON for ${currentTool.name}: ${e}`);
|
219
|
+
}
|
220
|
+
}
|
175
221
|
onEvent({
|
176
222
|
type: "tool_call_complete",
|
177
223
|
toolCall: {
|
@@ -182,8 +228,45 @@ onEvent, onComplete) {
|
|
182
228
|
});
|
183
229
|
}
|
184
230
|
}
|
231
|
+
else if (chunk.type === "message_stop" && activeContentBlock) {
|
232
|
+
// Handle Anthropic bug: message_stop without content_block_stop
|
233
|
+
console.warn(`[Anthropic] Received message_stop without content_block_stop - handling as implicit block stop`);
|
234
|
+
activeContentBlock = false;
|
235
|
+
// Emit synthetic content_block_stop for the current tool
|
236
|
+
const currentTool = toolCalls[toolCalls.length - 1];
|
237
|
+
if (currentTool) {
|
238
|
+
// Log the incomplete tool
|
239
|
+
console.warn(`[Anthropic] Synthetic content_block_stop for incomplete tool ${currentTool.name} (${currentTool.arguments.length} chars)`);
|
240
|
+
// Only emit tool_call_complete if we have valid JSON
|
241
|
+
if (isValidJSON(currentTool.arguments)) {
|
242
|
+
onEvent({
|
243
|
+
type: "tool_call_complete",
|
244
|
+
toolCall: {
|
245
|
+
id: currentTool.id,
|
246
|
+
name: currentTool.name,
|
247
|
+
arguments: currentTool.arguments,
|
248
|
+
},
|
249
|
+
});
|
250
|
+
}
|
251
|
+
else {
|
252
|
+
console.error(`[Anthropic] Tool ${currentTool.name} has incomplete JSON, skipping tool_call_complete event`);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
}
|
185
256
|
}
|
186
|
-
|
257
|
+
// Final check: filter out any remaining incomplete tool calls
|
258
|
+
const validToolCalls = toolCalls.filter((tc, idx) => {
|
259
|
+
if (!isValidJSON(tc.arguments)) {
|
260
|
+
console.warn(`[Anthropic] Filtering out incomplete tool call ${idx} (${tc.name}) with INVALID JSON (${tc.arguments.length} chars)`);
|
261
|
+
return false;
|
262
|
+
}
|
263
|
+
return true;
|
264
|
+
});
|
265
|
+
if (toolCalls.length !== validToolCalls.length) {
|
266
|
+
console.log(`[Anthropic] Filtered out ${toolCalls.length - validToolCalls.length} incomplete tool calls`);
|
267
|
+
console.log(`[Anthropic] Successfully processed ${validToolCalls.length} valid tool calls`);
|
268
|
+
}
|
269
|
+
onComplete(fullMessage, validToolCalls);
|
187
270
|
}
|
188
271
|
catch (error) {
|
189
272
|
onEvent({
|