graphlit-client 1.0.20260420001 → 1.0.20260420002
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.d.ts +5 -26
- package/dist/client.js +148 -264
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -3096,6 +3096,11 @@ declare class Graphlit {
|
|
|
3096
3096
|
* Tool calls on assistant messages are extracted for stuck-pattern tracking.
|
|
3097
3097
|
*/
|
|
3098
3098
|
private reconstructTurnResults;
|
|
3099
|
+
/**
|
|
3100
|
+
* Detect whether one tool call is a task_complete invocation, either
|
|
3101
|
+
* directly or through a meta-executor such as execute_tool.
|
|
3102
|
+
*/
|
|
3103
|
+
private isTaskCompleteToolCall;
|
|
3099
3104
|
/**
|
|
3100
3105
|
* Detect whether task_complete was invoked in a single list of tool calls.
|
|
3101
3106
|
*
|
|
@@ -3119,32 +3124,6 @@ declare class Graphlit {
|
|
|
3119
3124
|
* with `intermediateMessages` rather than a single tool-call list.
|
|
3120
3125
|
*/
|
|
3121
3126
|
private detectTaskCompleteInMessages;
|
|
3122
|
-
/**
|
|
3123
|
-
* Extract the `final_message` argument from a task_complete invocation
|
|
3124
|
-
* within a list of tool calls. Handles both direct invocation and
|
|
3125
|
-
* meta-executor wrapping (mirrors `detectTaskComplete`).
|
|
3126
|
-
*
|
|
3127
|
-
* Returns undefined if task_complete was not called, the arg is missing,
|
|
3128
|
-
* or the arg is not a non-empty string.
|
|
3129
|
-
*/
|
|
3130
|
-
private extractTaskCompleteFinalMessage;
|
|
3131
|
-
/**
|
|
3132
|
-
* Scan a list of messages for a task_complete invocation's `final_message`
|
|
3133
|
-
* arg. Convenience wrapper over `extractTaskCompleteFinalMessage`.
|
|
3134
|
-
*/
|
|
3135
|
-
private extractTaskCompleteFinalMessageFromMessages;
|
|
3136
|
-
/**
|
|
3137
|
-
* Sanitize task_complete's `final_message` payload out of persisted
|
|
3138
|
-
* intermediate messages. The tool call record (name, id, timing, ordering)
|
|
3139
|
-
* is preserved as a completion marker, but its `final_message` arg is
|
|
3140
|
-
* stripped because that content is already stored as the turn's assistant
|
|
3141
|
-
* text via `completion`. Keeping the tool call without the payload yields
|
|
3142
|
-
* the same storage win as full removal while preserving the trace.
|
|
3143
|
-
*
|
|
3144
|
-
* Handles both direct and meta-executor (`execute_tool({tool: ...})`)
|
|
3145
|
-
* invocations, matching `detectTaskComplete`.
|
|
3146
|
-
*/
|
|
3147
|
-
private sanitizeTaskCompletePayload;
|
|
3148
3127
|
/**
|
|
3149
3128
|
* Run LLM-as-judge quality assessment on a completed agent run.
|
|
3150
3129
|
*/
|
package/dist/client.js
CHANGED
|
@@ -3059,7 +3059,11 @@ class Graphlit {
|
|
|
3059
3059
|
* @returns The result of the deletion.
|
|
3060
3060
|
*/
|
|
3061
3061
|
async deleteAllSkills(filter, isSynchronous, correlationId) {
|
|
3062
|
-
return this.mutateAndCheckError(Documents.DeleteAllSkills, {
|
|
3062
|
+
return this.mutateAndCheckError(Documents.DeleteAllSkills, {
|
|
3063
|
+
filter: filter,
|
|
3064
|
+
isSynchronous: isSynchronous,
|
|
3065
|
+
correlationId: correlationId,
|
|
3066
|
+
});
|
|
3063
3067
|
}
|
|
3064
3068
|
/**
|
|
3065
3069
|
* Enables a skill.
|
|
@@ -3111,7 +3115,10 @@ class Graphlit {
|
|
|
3111
3115
|
* @returns The updated collections.
|
|
3112
3116
|
*/
|
|
3113
3117
|
async addSkillsToCollections(skills, collections) {
|
|
3114
|
-
return this.mutateAndCheckError(Documents.AddSkillsToCollections, {
|
|
3118
|
+
return this.mutateAndCheckError(Documents.AddSkillsToCollections, {
|
|
3119
|
+
skills: skills,
|
|
3120
|
+
collections: collections,
|
|
3121
|
+
});
|
|
3115
3122
|
}
|
|
3116
3123
|
/**
|
|
3117
3124
|
* Removes skills from a collection.
|
|
@@ -3120,7 +3127,10 @@ class Graphlit {
|
|
|
3120
3127
|
* @returns The updated collection.
|
|
3121
3128
|
*/
|
|
3122
3129
|
async removeSkillsFromCollection(skills, collection) {
|
|
3123
|
-
return this.mutateAndCheckError(Documents.RemoveSkillsFromCollection, {
|
|
3130
|
+
return this.mutateAndCheckError(Documents.RemoveSkillsFromCollection, {
|
|
3131
|
+
skills: skills,
|
|
3132
|
+
collection: collection,
|
|
3133
|
+
});
|
|
3124
3134
|
}
|
|
3125
3135
|
/**
|
|
3126
3136
|
* Prompts multiple specifications and returns the best response.
|
|
@@ -5730,7 +5740,8 @@ class Graphlit {
|
|
|
5730
5740
|
});
|
|
5731
5741
|
}
|
|
5732
5742
|
// Emit tool events if there were tool calls
|
|
5733
|
-
if (promptResult.toolResults &&
|
|
5743
|
+
if (promptResult.toolResults &&
|
|
5744
|
+
promptResult.toolResults.length > 0) {
|
|
5734
5745
|
for (const toolResult of promptResult.toolResults) {
|
|
5735
5746
|
const toolCall = buildConversationToolCallFromResult(toolResult);
|
|
5736
5747
|
onEvent({
|
|
@@ -6039,10 +6050,7 @@ class Graphlit {
|
|
|
6039
6050
|
completionInput.thinkingSignature =
|
|
6040
6051
|
loopResult.lastRoundReasoning.signature;
|
|
6041
6052
|
}
|
|
6042
|
-
finalMessageInputs = [
|
|
6043
|
-
...(finalMessageInputs || []),
|
|
6044
|
-
completionInput,
|
|
6045
|
-
];
|
|
6053
|
+
finalMessageInputs = [...(finalMessageInputs || []), completionInput];
|
|
6046
6054
|
}
|
|
6047
6055
|
const completeResponse = await this.completeConversation(trimmedMessage, conversationId, millisecondsToTimeSpan(completionTime), millisecondsToTimeSpan(ttft), throughput, collectedArtifacts.length > 0 ? collectedArtifacts : undefined, finalMessageInputs, correlationId);
|
|
6048
6056
|
finalTokens =
|
|
@@ -6063,7 +6071,7 @@ class Graphlit {
|
|
|
6063
6071
|
* Shared between streamAgent (single turn) and runAgent (multi-turn harness).
|
|
6064
6072
|
*/
|
|
6065
6073
|
async executeStreamingLoop(config) {
|
|
6066
|
-
const { conversationId, specification, tools, toolHandlers, uiAdapter, budgetTracker, contextStrategy: { toolResultTokenLimit, toolRoundLimit, rebudgetThreshold }, maxRounds, abortSignal, useResponsesApi, mimeType, data, correlationId, persona, } = config;
|
|
6074
|
+
const { conversationId, specification, tools, toolHandlers, uiAdapter, budgetTracker, contextStrategy: { toolResultTokenLimit, toolRoundLimit, rebudgetThreshold, }, maxRounds, abortSignal, useResponsesApi, mimeType, data, correlationId, persona, } = config;
|
|
6067
6075
|
let messages = config.messages;
|
|
6068
6076
|
let currentRound = 0;
|
|
6069
6077
|
let fullMessage = "";
|
|
@@ -6176,7 +6184,7 @@ class Graphlit {
|
|
|
6176
6184
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING_MESSAGES) {
|
|
6177
6185
|
console.log(`🔍 [OpenAI Responses] Sending ${openAIResponsesState.initialInput.length} initial items and ${openAIResponsesState.continuationItems.length} continuation items`);
|
|
6178
6186
|
}
|
|
6179
|
-
const responsesResult = await this.streamWithOpenAIResponses(specification, messages, openAIResponsesPendingToolMessages, tools, uiAdapter, abortSignal, openAIResponsesState,
|
|
6187
|
+
const responsesResult = await this.streamWithOpenAIResponses(specification, messages, openAIResponsesPendingToolMessages, tools, uiAdapter, abortSignal, openAIResponsesState, currentRound === 0 && tools?.length ? "required" : undefined);
|
|
6180
6188
|
roundMessage = responsesResult.message;
|
|
6181
6189
|
toolCalls = responsesResult.toolCalls;
|
|
6182
6190
|
openAIResponsesState = responsesResult.state;
|
|
@@ -6314,7 +6322,10 @@ class Graphlit {
|
|
|
6314
6322
|
}
|
|
6315
6323
|
const mistralMessages = formatMessagesForMistral(messages);
|
|
6316
6324
|
// ALWAYS log when there's a tool-related issue for debugging
|
|
6317
|
-
const hasToolCalls = mistralMessages.some((m) => "tool_calls" in m &&
|
|
6325
|
+
const hasToolCalls = mistralMessages.some((m) => "tool_calls" in m &&
|
|
6326
|
+
Array.isArray(m.tool_calls) &&
|
|
6327
|
+
m
|
|
6328
|
+
.tool_calls.length > 0);
|
|
6318
6329
|
const hasToolResponses = mistralMessages.some((m) => m.role === "tool");
|
|
6319
6330
|
// Count tool responses to determine if we should pass tools
|
|
6320
6331
|
const toolResponseCount = mistralMessages.filter((m) => m.role === "tool").length;
|
|
@@ -6325,7 +6336,8 @@ class Graphlit {
|
|
|
6325
6336
|
console.log(JSON.stringify(mistralMessages, null, 2));
|
|
6326
6337
|
// Count tool calls and responses
|
|
6327
6338
|
const toolCallCount = mistralMessages.reduce((count, m) => {
|
|
6328
|
-
const calls = m
|
|
6339
|
+
const calls = m
|
|
6340
|
+
.tool_calls;
|
|
6329
6341
|
return count + (Array.isArray(calls) ? calls.length : 0);
|
|
6330
6342
|
}, 0);
|
|
6331
6343
|
console.log(`🔍 [Mistral] Tool calls: ${toolCallCount}, Tool responses: ${toolResponseCount}`);
|
|
@@ -6432,13 +6444,11 @@ class Graphlit {
|
|
|
6432
6444
|
if (abortSignal?.aborted)
|
|
6433
6445
|
throw retryError;
|
|
6434
6446
|
const isRetryable = retryError instanceof ProviderError && retryError.retryable;
|
|
6435
|
-
if (!isRetryable ||
|
|
6436
|
-
providerAttempt >= DEFAULT_PROVIDER_RETRIES) {
|
|
6447
|
+
if (!isRetryable || providerAttempt >= DEFAULT_PROVIDER_RETRIES) {
|
|
6437
6448
|
throw retryError;
|
|
6438
6449
|
}
|
|
6439
6450
|
// Exponential backoff with jitter
|
|
6440
|
-
const delay = Math.min(PROVIDER_RETRY_BASE_DELAY_MS *
|
|
6441
|
-
Math.pow(2, providerAttempt), PROVIDER_RETRY_MAX_DELAY_MS);
|
|
6451
|
+
const delay = Math.min(PROVIDER_RETRY_BASE_DELAY_MS * Math.pow(2, providerAttempt), PROVIDER_RETRY_MAX_DELAY_MS);
|
|
6442
6452
|
const jitter = Math.random() * delay * 0.1;
|
|
6443
6453
|
const totalDelay = Math.round(delay + jitter);
|
|
6444
6454
|
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
@@ -6467,42 +6477,61 @@ class Graphlit {
|
|
|
6467
6477
|
if (!toolCalls || toolCalls.length === 0) {
|
|
6468
6478
|
break;
|
|
6469
6479
|
}
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
console.log(
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6480
|
+
if (process.env.DEBUG_GRAPHLIT_SDK_STREAMING) {
|
|
6481
|
+
console.log(`\n🔧 [executeStreamingLoop] Round ${currentRound}: Processing ${toolCalls.length} tool calls`);
|
|
6482
|
+
toolCalls.forEach((tc, idx) => {
|
|
6483
|
+
console.log(` ${idx + 1}. ${tc.name} (${tc.id}) - Args length: ${tc.arguments.length}`);
|
|
6484
|
+
});
|
|
6485
|
+
}
|
|
6486
|
+
// Add assistant message with tool calls to conversation. This is done
|
|
6487
|
+
// before terminal task_complete detection so the outer runAgent loop can
|
|
6488
|
+
// still observe the completion signal in intermediateMessages.
|
|
6489
|
+
const roundToolCalls = toolCalls.map(normalizeToolCallForExecution);
|
|
6490
|
+
const assistantMessage = {
|
|
6491
|
+
__typename: "ConversationMessage",
|
|
6492
|
+
role: Types.ConversationRoleTypes.Assistant,
|
|
6493
|
+
message: roundMessage,
|
|
6494
|
+
toolCalls: roundToolCalls,
|
|
6495
|
+
timestamp: new Date().toISOString(),
|
|
6496
|
+
};
|
|
6497
|
+
if (roundReasoning) {
|
|
6498
|
+
assistantMessage.thinkingContent = roundReasoning.content;
|
|
6499
|
+
if (roundReasoning.signature) {
|
|
6500
|
+
assistantMessage.thinkingSignature = roundReasoning.signature;
|
|
6477
6501
|
}
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6502
|
+
reasoningByMessageIndex.set(messages.length, roundReasoning);
|
|
6503
|
+
}
|
|
6504
|
+
messages.push(assistantMessage);
|
|
6505
|
+
// Track tool names for stuck detection
|
|
6506
|
+
for (const tc of roundToolCalls) {
|
|
6507
|
+
toolCallNames.push(tc.name);
|
|
6508
|
+
}
|
|
6509
|
+
totalToolCallCount += roundToolCalls.length;
|
|
6510
|
+
// Track assistant message in budget
|
|
6511
|
+
if (budgetTracker) {
|
|
6512
|
+
const assistantTokens = estimateTokens(roundMessage) +
|
|
6513
|
+
roundToolCalls.reduce((sum, tc) => sum + estimateTokens(tc.arguments), 0);
|
|
6514
|
+
budgetTracker.addMessage("", assistantTokens);
|
|
6515
|
+
}
|
|
6516
|
+
// task_complete is terminal. Do not invoke its handler and feed an ack
|
|
6517
|
+
// back to the model; that extra round can produce trailing filler which
|
|
6518
|
+
// overwrites finalAssistantMessage.
|
|
6519
|
+
if (this.detectTaskComplete(roundToolCalls)) {
|
|
6520
|
+
const terminalAt = nowIsoString();
|
|
6521
|
+
for (const toolCall of roundToolCalls) {
|
|
6522
|
+
if (this.isTaskCompleteToolCall(toolCall)) {
|
|
6523
|
+
toolCall.startedAt = toolCall.startedAt ?? terminalAt;
|
|
6524
|
+
toolCall.completedAt = toolCall.completedAt ?? terminalAt;
|
|
6525
|
+
toolCall.durationMs = toolCall.durationMs ?? 0;
|
|
6526
|
+
toolCall.firstStatusAt =
|
|
6527
|
+
toolCall.firstStatusAt ?? toolCall.startedAt;
|
|
6528
|
+
toolCall.status = Types.ToolExecutionStatus.Completed;
|
|
6491
6529
|
}
|
|
6492
|
-
reasoningByMessageIndex.set(messages.length, roundReasoning);
|
|
6493
|
-
}
|
|
6494
|
-
messages.push(assistantMessage);
|
|
6495
|
-
// Track tool names for stuck detection
|
|
6496
|
-
for (const tc of roundToolCalls) {
|
|
6497
|
-
toolCallNames.push(tc.name);
|
|
6498
|
-
}
|
|
6499
|
-
totalToolCallCount += roundToolCalls.length;
|
|
6500
|
-
// Track assistant message in budget
|
|
6501
|
-
if (budgetTracker) {
|
|
6502
|
-
const assistantTokens = estimateTokens(roundMessage) +
|
|
6503
|
-
roundToolCalls.reduce((sum, tc) => sum + estimateTokens(tc.arguments), 0);
|
|
6504
|
-
budgetTracker.addMessage("", assistantTokens);
|
|
6505
6530
|
}
|
|
6531
|
+
break;
|
|
6532
|
+
}
|
|
6533
|
+
// Execute tools and prepare for next round
|
|
6534
|
+
if (toolHandlers) {
|
|
6506
6535
|
const toolExecutionResults = new Array(roundToolCalls.length);
|
|
6507
6536
|
const executeStreamingToolCall = async (toolCall, index) => {
|
|
6508
6537
|
if (abortSignal?.aborted) {
|
|
@@ -6516,7 +6545,8 @@ class Graphlit {
|
|
|
6516
6545
|
toolCall.completedAt = terminalAt;
|
|
6517
6546
|
toolCall.durationMs = 0;
|
|
6518
6547
|
toolCall.failedAt = terminalAt;
|
|
6519
|
-
toolCall.firstStatusAt =
|
|
6548
|
+
toolCall.firstStatusAt =
|
|
6549
|
+
toolCall.firstStatusAt ?? toolCall.startedAt;
|
|
6520
6550
|
toolCall.status = Types.ToolExecutionStatus.Failed;
|
|
6521
6551
|
uiAdapter.handleEvent({
|
|
6522
6552
|
type: "tool_call_complete",
|
|
@@ -6611,7 +6641,8 @@ class Graphlit {
|
|
|
6611
6641
|
toolCall.completedAt = terminalAt;
|
|
6612
6642
|
toolCall.durationMs = 0;
|
|
6613
6643
|
toolCall.failedAt = terminalAt;
|
|
6614
|
-
toolCall.firstStatusAt =
|
|
6644
|
+
toolCall.firstStatusAt =
|
|
6645
|
+
toolCall.firstStatusAt ?? toolCall.startedAt;
|
|
6615
6646
|
toolCall.status = Types.ToolExecutionStatus.Failed;
|
|
6616
6647
|
uiAdapter.handleEvent({
|
|
6617
6648
|
type: "tool_call_complete",
|
|
@@ -6647,7 +6678,8 @@ class Graphlit {
|
|
|
6647
6678
|
const executionStartMs = Date.now();
|
|
6648
6679
|
const executionStartedAt = new Date(executionStartMs).toISOString();
|
|
6649
6680
|
toolCall.startedAt = executionStartedAt;
|
|
6650
|
-
toolCall.firstStatusAt =
|
|
6681
|
+
toolCall.firstStatusAt =
|
|
6682
|
+
toolCall.firstStatusAt ?? executionStartedAt;
|
|
6651
6683
|
uiAdapter.handleEvent({
|
|
6652
6684
|
type: "tool_call_executing",
|
|
6653
6685
|
toolCall: {
|
|
@@ -6717,7 +6749,8 @@ class Graphlit {
|
|
|
6717
6749
|
toolCall.durationMs = Math.max(0, new Date(completedAt).getTime() -
|
|
6718
6750
|
new Date(toolCall.startedAt).getTime());
|
|
6719
6751
|
toolCall.failedAt = completedAt;
|
|
6720
|
-
toolCall.firstStatusAt =
|
|
6752
|
+
toolCall.firstStatusAt =
|
|
6753
|
+
toolCall.firstStatusAt ?? toolCall.startedAt;
|
|
6721
6754
|
toolCall.status = Types.ToolExecutionStatus.Failed;
|
|
6722
6755
|
uiAdapter.handleEvent({
|
|
6723
6756
|
type: "tool_call_complete",
|
|
@@ -6891,7 +6924,7 @@ class Graphlit {
|
|
|
6891
6924
|
let lastTurnUsage;
|
|
6892
6925
|
// Wrap onEvent to capture per-turn usage from conversation_completed events
|
|
6893
6926
|
const wrappedOnEvent = (event) => {
|
|
6894
|
-
if (event.type ===
|
|
6927
|
+
if (event.type === "conversation_completed" && event.usage) {
|
|
6895
6928
|
const u = event.usage;
|
|
6896
6929
|
lastTurnUsage = {
|
|
6897
6930
|
promptTokens: u.promptTokens || 0,
|
|
@@ -6960,7 +6993,8 @@ class Graphlit {
|
|
|
6960
6993
|
}
|
|
6961
6994
|
// Get full specification for streaming support check and complexity classification
|
|
6962
6995
|
const fullSpec = agentSpec?.id
|
|
6963
|
-
? (await this.getSpecification(agentSpec.id))
|
|
6996
|
+
? (await this.getSpecification(agentSpec.id))
|
|
6997
|
+
.specification
|
|
6964
6998
|
: undefined;
|
|
6965
6999
|
// Initialize evaluator and stuck detector
|
|
6966
7000
|
const evaluator = new TurnEvaluator({
|
|
@@ -6973,40 +7007,36 @@ class Graphlit {
|
|
|
6973
7007
|
const stuckDetector = new StuckDetector();
|
|
6974
7008
|
// Adaptive budget: classify task complexity via LLM
|
|
6975
7009
|
if (fullSpec) {
|
|
6976
|
-
const { multiplier } = await evaluator.classifyComplexity(prompt, (classPrompt, text, classTools) => this.extractText(classPrompt, text, classTools, undefined, undefined, options?.correlationId)
|
|
6977
|
-
.then((r) => r.extractText));
|
|
7010
|
+
const { multiplier } = await evaluator.classifyComplexity(prompt, (classPrompt, text, classTools) => this.extractText(classPrompt, text, classTools, undefined, undefined, options?.correlationId).then((r) => r.extractText));
|
|
6978
7011
|
if (multiplier > 1.0) {
|
|
6979
7012
|
evaluator.adjustBudget(multiplier);
|
|
6980
7013
|
}
|
|
6981
7014
|
}
|
|
6982
7015
|
// Build tools list with task_complete prepended.
|
|
6983
7016
|
//
|
|
6984
|
-
// task_complete
|
|
6985
|
-
//
|
|
6986
|
-
//
|
|
6987
|
-
//
|
|
7017
|
+
// task_complete is a bare completion signal — it takes no arguments.
|
|
7018
|
+
// The agent's user-facing answer MUST be in its final assistant message
|
|
7019
|
+
// (the `fullMessage` / `finalAssistantMessage` on the turn result); this
|
|
7020
|
+
// tool only marks the run as done.
|
|
6988
7021
|
//
|
|
6989
|
-
//
|
|
6990
|
-
//
|
|
6991
|
-
// the
|
|
6992
|
-
//
|
|
6993
|
-
//
|
|
7022
|
+
// The streaming loop treats task_complete as TERMINAL: when a round's
|
|
7023
|
+
// tool calls include task_complete, the loop breaks immediately without
|
|
7024
|
+
// invoking the handler or starting another round. That prevents the
|
|
7025
|
+
// model from emitting trailing filler ("delivered above", "see the
|
|
7026
|
+
// response") after the tool-call ack, which would otherwise overwrite
|
|
7027
|
+
// the real answer in `finalAssistantMessage`.
|
|
6994
7028
|
//
|
|
6995
|
-
//
|
|
6996
|
-
//
|
|
6997
|
-
//
|
|
6998
|
-
//
|
|
7029
|
+
// Earlier versions had a `summary` field. That consistently caused the
|
|
7030
|
+
// model to stuff its answer into the tool argument and then write a
|
|
7031
|
+
// useless prose message ("answer delivered above") — the user would see
|
|
7032
|
+
// the empty prose, not the answer. Removing the field eliminates the
|
|
7033
|
+
// hiding place.
|
|
6999
7034
|
const taskCompleteTool = {
|
|
7000
7035
|
name: "task_complete",
|
|
7001
|
-
description: "Signal that you have completed the assigned task.
|
|
7036
|
+
description: "Signal that you have completed the assigned task. Your full answer must already be in your assistant message BEFORE calling this — that prose is what the user sees. This tool takes no arguments; it only ends the run.",
|
|
7002
7037
|
schema: JSON.stringify({
|
|
7003
7038
|
type: "object",
|
|
7004
|
-
properties: {
|
|
7005
|
-
final_message: {
|
|
7006
|
-
type: "string",
|
|
7007
|
-
description: "Your complete, final answer to the user as full prose. Replaces any assistant text written around this tool call.",
|
|
7008
|
-
},
|
|
7009
|
-
},
|
|
7039
|
+
properties: {},
|
|
7010
7040
|
}),
|
|
7011
7041
|
};
|
|
7012
7042
|
const allTools = [
|
|
@@ -7172,8 +7202,7 @@ class Graphlit {
|
|
|
7172
7202
|
messageToAdd.toolCallResponse =
|
|
7173
7203
|
historyMessage.toolCallResponse;
|
|
7174
7204
|
if (historyMessage.thinkingContent)
|
|
7175
|
-
messageToAdd.thinkingContent =
|
|
7176
|
-
historyMessage.thinkingContent;
|
|
7205
|
+
messageToAdd.thinkingContent = historyMessage.thinkingContent;
|
|
7177
7206
|
if (historyMessage.thinkingSignature)
|
|
7178
7207
|
messageToAdd.thinkingSignature =
|
|
7179
7208
|
historyMessage.thinkingSignature;
|
|
@@ -7264,39 +7293,13 @@ class Graphlit {
|
|
|
7264
7293
|
finally {
|
|
7265
7294
|
uiAdapter.dispose();
|
|
7266
7295
|
}
|
|
7267
|
-
// 8. Detect task_complete
|
|
7268
|
-
//
|
|
7269
|
-
//
|
|
7270
|
-
// tool schema above.
|
|
7296
|
+
// 8. Detect task_complete before persistence. The streaming loop
|
|
7297
|
+
// records the terminal assistant/tool-call round, but does not execute
|
|
7298
|
+
// the completion handler or start a follow-up filler round.
|
|
7271
7299
|
const taskCompleteThisTurn = this.detectTaskCompleteInMessages(loopResult.intermediateMessages);
|
|
7272
|
-
const
|
|
7273
|
-
? this.extractTaskCompleteFinalMessageFromMessages(loopResult.intermediateMessages)
|
|
7274
|
-
: undefined;
|
|
7275
|
-
// Resolve the assistant message to persist for this turn:
|
|
7276
|
-
// - task_complete({final_message}) → final_message (authoritative)
|
|
7277
|
-
// - task_complete() with no arg, prior substantive answer exists →
|
|
7278
|
-
// drop the turn's prose so throwaway text like "delivered above"
|
|
7279
|
-
// doesn't pollute chat history
|
|
7280
|
-
// - task_complete() with no arg, no prior answer → keep the prose
|
|
7281
|
-
// (single-shot completion case)
|
|
7282
|
-
// - normal turn → keep the prose
|
|
7283
|
-
let assistantMessageToPersist;
|
|
7284
|
-
if (taskCompleteThisTurn) {
|
|
7285
|
-
if (taskCompleteFinalMessage) {
|
|
7286
|
-
assistantMessageToPersist = taskCompleteFinalMessage;
|
|
7287
|
-
}
|
|
7288
|
-
else if (finalMessage) {
|
|
7289
|
-
assistantMessageToPersist = "";
|
|
7290
|
-
}
|
|
7291
|
-
else {
|
|
7292
|
-
assistantMessageToPersist = loopResult.finalAssistantMessage;
|
|
7293
|
-
}
|
|
7294
|
-
}
|
|
7295
|
-
else {
|
|
7296
|
-
assistantMessageToPersist = loopResult.finalAssistantMessage;
|
|
7297
|
-
}
|
|
7300
|
+
const trimmedMessage = loopResult.finalAssistantMessage;
|
|
7298
7301
|
// 8b. Complete conversation (persist turn)
|
|
7299
|
-
if (
|
|
7302
|
+
if (trimmedMessage) {
|
|
7300
7303
|
const completionTime = uiAdapter.getCompletionTime();
|
|
7301
7304
|
const ttft = uiAdapter.getTTFT();
|
|
7302
7305
|
const throughput = uiAdapter.getThroughput();
|
|
@@ -7305,20 +7308,13 @@ class Graphlit {
|
|
|
7305
7308
|
return undefined;
|
|
7306
7309
|
return `PT${ms / 1000}S`;
|
|
7307
7310
|
};
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
// keeping it in the tool-call arguments would double-store it.
|
|
7311
|
-
// The tool call record itself stays as a completion marker.
|
|
7312
|
-
const sanitizedIntermediates = taskCompleteThisTurn
|
|
7313
|
-
? this.sanitizeTaskCompletePayload(loopResult.intermediateMessages)
|
|
7314
|
-
: loopResult.intermediateMessages;
|
|
7315
|
-
let turnMessageInputs = sanitizedIntermediates.length > 0
|
|
7316
|
-
? sanitizedIntermediates
|
|
7311
|
+
let turnMessageInputs = loopResult.intermediateMessages.length > 0
|
|
7312
|
+
? loopResult.intermediateMessages
|
|
7317
7313
|
: undefined;
|
|
7318
7314
|
if (loopResult.lastRoundReasoning) {
|
|
7319
7315
|
const completionInput = {
|
|
7320
7316
|
role: Types.ConversationRoleTypes.Assistant,
|
|
7321
|
-
message:
|
|
7317
|
+
message: trimmedMessage,
|
|
7322
7318
|
timestamp: new Date().toISOString(),
|
|
7323
7319
|
thinkingContent: loopResult.lastRoundReasoning.content,
|
|
7324
7320
|
};
|
|
@@ -7326,12 +7322,9 @@ class Graphlit {
|
|
|
7326
7322
|
completionInput.thinkingSignature =
|
|
7327
7323
|
loopResult.lastRoundReasoning.signature;
|
|
7328
7324
|
}
|
|
7329
|
-
turnMessageInputs = [
|
|
7330
|
-
...(turnMessageInputs || []),
|
|
7331
|
-
completionInput,
|
|
7332
|
-
];
|
|
7325
|
+
turnMessageInputs = [...(turnMessageInputs || []), completionInput];
|
|
7333
7326
|
}
|
|
7334
|
-
await this.completeConversation(
|
|
7327
|
+
await this.completeConversation(trimmedMessage, conversationId, millisecondsToTimeSpan(completionTime), millisecondsToTimeSpan(ttft), throughput, undefined, turnMessageInputs, options?.correlationId);
|
|
7335
7328
|
// Emit completion event
|
|
7336
7329
|
uiAdapter.handleEvent({
|
|
7337
7330
|
type: "complete",
|
|
@@ -7344,7 +7337,7 @@ class Graphlit {
|
|
|
7344
7337
|
const turnResult = {
|
|
7345
7338
|
turnNumber: turn,
|
|
7346
7339
|
prompt: currentPrompt,
|
|
7347
|
-
responseText:
|
|
7340
|
+
responseText: loopResult.fullMessage,
|
|
7348
7341
|
toolCalls: turnToolNames,
|
|
7349
7342
|
toolCallCount: loopResult.toolCallCount,
|
|
7350
7343
|
durationMs: turnDuration,
|
|
@@ -7360,25 +7353,8 @@ class Graphlit {
|
|
|
7360
7353
|
lastTurnUsage = undefined;
|
|
7361
7354
|
turnResults.push(turnResult);
|
|
7362
7355
|
totalToolCalls += loopResult.toolCallCount;
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
// - task_complete() without arg → keep prior finalMessage (avoids
|
|
7366
|
-
// throwaway completion-turn prose overwriting a real answer)
|
|
7367
|
-
// - normal turn → this turn's assistant text
|
|
7368
|
-
if (taskCompleteThisTurn) {
|
|
7369
|
-
if (taskCompleteFinalMessage) {
|
|
7370
|
-
finalMessage = taskCompleteFinalMessage;
|
|
7371
|
-
}
|
|
7372
|
-
else if (!finalMessage) {
|
|
7373
|
-
// No prior answer — this turn's prose is all we have
|
|
7374
|
-
finalMessage =
|
|
7375
|
-
loopResult.finalAssistantMessage || loopResult.fullMessage;
|
|
7376
|
-
}
|
|
7377
|
-
}
|
|
7378
|
-
else {
|
|
7379
|
-
finalMessage =
|
|
7380
|
-
loopResult.finalAssistantMessage || loopResult.fullMessage;
|
|
7381
|
-
}
|
|
7356
|
+
finalMessage =
|
|
7357
|
+
loopResult.finalAssistantMessage || loopResult.fullMessage;
|
|
7382
7358
|
lastContextWindow = loopResult.contextWindow;
|
|
7383
7359
|
// 10. Notify callback
|
|
7384
7360
|
options?.onTurnComplete?.(turnResult);
|
|
@@ -7463,8 +7439,7 @@ class Graphlit {
|
|
|
7463
7439
|
}
|
|
7464
7440
|
else {
|
|
7465
7441
|
status = "error";
|
|
7466
|
-
errorMessage =
|
|
7467
|
-
error instanceof Error ? error.message : "Unknown error";
|
|
7442
|
+
errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
7468
7443
|
}
|
|
7469
7444
|
return this.buildRunAgentResult({
|
|
7470
7445
|
agentId: resolvedAgentId,
|
|
@@ -7577,6 +7552,28 @@ class Graphlit {
|
|
|
7577
7552
|
}
|
|
7578
7553
|
return results;
|
|
7579
7554
|
}
|
|
7555
|
+
/**
|
|
7556
|
+
* Detect whether one tool call is a task_complete invocation, either
|
|
7557
|
+
* directly or through a meta-executor such as execute_tool.
|
|
7558
|
+
*/
|
|
7559
|
+
isTaskCompleteToolCall(toolCall) {
|
|
7560
|
+
if (!toolCall)
|
|
7561
|
+
return false;
|
|
7562
|
+
// Direct invocation
|
|
7563
|
+
if (toolCall.name === "task_complete")
|
|
7564
|
+
return true;
|
|
7565
|
+
// Meta-executor wrapping (e.g. execute_tool({ tool: "task_complete", parameters: {...} }))
|
|
7566
|
+
if (toolCall.arguments) {
|
|
7567
|
+
try {
|
|
7568
|
+
const args = JSON.parse(toolCall.arguments);
|
|
7569
|
+
return args.tool === "task_complete";
|
|
7570
|
+
}
|
|
7571
|
+
catch {
|
|
7572
|
+
// Not JSON — skip
|
|
7573
|
+
}
|
|
7574
|
+
}
|
|
7575
|
+
return false;
|
|
7576
|
+
}
|
|
7580
7577
|
/**
|
|
7581
7578
|
* Detect whether task_complete was invoked in a single list of tool calls.
|
|
7582
7579
|
*
|
|
@@ -7597,22 +7594,8 @@ class Graphlit {
|
|
|
7597
7594
|
if (!toolCalls)
|
|
7598
7595
|
return false;
|
|
7599
7596
|
for (const tc of toolCalls) {
|
|
7600
|
-
if (
|
|
7601
|
-
continue;
|
|
7602
|
-
// Direct invocation
|
|
7603
|
-
if (tc.name === "task_complete")
|
|
7597
|
+
if (this.isTaskCompleteToolCall(tc))
|
|
7604
7598
|
return true;
|
|
7605
|
-
// Meta-executor wrapping (e.g. execute_tool({ tool: "task_complete", parameters: {...} }))
|
|
7606
|
-
if (tc.arguments) {
|
|
7607
|
-
try {
|
|
7608
|
-
const args = JSON.parse(tc.arguments);
|
|
7609
|
-
if (args.tool === "task_complete")
|
|
7610
|
-
return true;
|
|
7611
|
-
}
|
|
7612
|
-
catch {
|
|
7613
|
-
// Not JSON — skip
|
|
7614
|
-
}
|
|
7615
|
-
}
|
|
7616
7599
|
}
|
|
7617
7600
|
return false;
|
|
7618
7601
|
}
|
|
@@ -7628,107 +7611,6 @@ class Graphlit {
|
|
|
7628
7611
|
}
|
|
7629
7612
|
return false;
|
|
7630
7613
|
}
|
|
7631
|
-
/**
|
|
7632
|
-
* Extract the `final_message` argument from a task_complete invocation
|
|
7633
|
-
* within a list of tool calls. Handles both direct invocation and
|
|
7634
|
-
* meta-executor wrapping (mirrors `detectTaskComplete`).
|
|
7635
|
-
*
|
|
7636
|
-
* Returns undefined if task_complete was not called, the arg is missing,
|
|
7637
|
-
* or the arg is not a non-empty string.
|
|
7638
|
-
*/
|
|
7639
|
-
extractTaskCompleteFinalMessage(toolCalls) {
|
|
7640
|
-
if (!toolCalls)
|
|
7641
|
-
return undefined;
|
|
7642
|
-
for (const tc of toolCalls) {
|
|
7643
|
-
if (!tc?.arguments)
|
|
7644
|
-
continue;
|
|
7645
|
-
let args;
|
|
7646
|
-
try {
|
|
7647
|
-
args = JSON.parse(tc.arguments);
|
|
7648
|
-
}
|
|
7649
|
-
catch {
|
|
7650
|
-
continue;
|
|
7651
|
-
}
|
|
7652
|
-
// Direct invocation: args ARE the task_complete params
|
|
7653
|
-
if (tc.name === "task_complete") {
|
|
7654
|
-
const fm = args.final_message;
|
|
7655
|
-
if (typeof fm === "string" && fm.trim().length > 0)
|
|
7656
|
-
return fm;
|
|
7657
|
-
continue;
|
|
7658
|
-
}
|
|
7659
|
-
// Meta-executor wrapping: { tool: "task_complete", parameters: {...} }
|
|
7660
|
-
if (args.tool === "task_complete") {
|
|
7661
|
-
const params = args.parameters;
|
|
7662
|
-
const fm = params?.final_message;
|
|
7663
|
-
if (typeof fm === "string" && fm.trim().length > 0)
|
|
7664
|
-
return fm;
|
|
7665
|
-
}
|
|
7666
|
-
}
|
|
7667
|
-
return undefined;
|
|
7668
|
-
}
|
|
7669
|
-
/**
|
|
7670
|
-
* Scan a list of messages for a task_complete invocation's `final_message`
|
|
7671
|
-
* arg. Convenience wrapper over `extractTaskCompleteFinalMessage`.
|
|
7672
|
-
*/
|
|
7673
|
-
extractTaskCompleteFinalMessageFromMessages(messages) {
|
|
7674
|
-
for (const msg of messages) {
|
|
7675
|
-
const fm = this.extractTaskCompleteFinalMessage(msg.toolCalls);
|
|
7676
|
-
if (fm)
|
|
7677
|
-
return fm;
|
|
7678
|
-
}
|
|
7679
|
-
return undefined;
|
|
7680
|
-
}
|
|
7681
|
-
/**
|
|
7682
|
-
* Sanitize task_complete's `final_message` payload out of persisted
|
|
7683
|
-
* intermediate messages. The tool call record (name, id, timing, ordering)
|
|
7684
|
-
* is preserved as a completion marker, but its `final_message` arg is
|
|
7685
|
-
* stripped because that content is already stored as the turn's assistant
|
|
7686
|
-
* text via `completion`. Keeping the tool call without the payload yields
|
|
7687
|
-
* the same storage win as full removal while preserving the trace.
|
|
7688
|
-
*
|
|
7689
|
-
* Handles both direct and meta-executor (`execute_tool({tool: ...})`)
|
|
7690
|
-
* invocations, matching `detectTaskComplete`.
|
|
7691
|
-
*/
|
|
7692
|
-
sanitizeTaskCompletePayload(messages) {
|
|
7693
|
-
return messages.map((msg) => {
|
|
7694
|
-
if (!msg.toolCalls || msg.toolCalls.length === 0)
|
|
7695
|
-
return msg;
|
|
7696
|
-
let mutated = false;
|
|
7697
|
-
const sanitizedToolCalls = msg.toolCalls.map((tc) => {
|
|
7698
|
-
if (!tc || !tc.arguments)
|
|
7699
|
-
return tc;
|
|
7700
|
-
let args;
|
|
7701
|
-
try {
|
|
7702
|
-
args = JSON.parse(tc.arguments);
|
|
7703
|
-
}
|
|
7704
|
-
catch {
|
|
7705
|
-
return tc;
|
|
7706
|
-
}
|
|
7707
|
-
// Direct invocation: args ARE the task_complete params
|
|
7708
|
-
if (tc.name === "task_complete" && "final_message" in args) {
|
|
7709
|
-
const { final_message: _dropped, ...rest } = args;
|
|
7710
|
-
void _dropped;
|
|
7711
|
-
mutated = true;
|
|
7712
|
-
return { ...tc, arguments: JSON.stringify(rest) };
|
|
7713
|
-
}
|
|
7714
|
-
// Meta-executor wrapping: { tool: "task_complete", parameters: {...} }
|
|
7715
|
-
if (args.tool === "task_complete") {
|
|
7716
|
-
const params = args.parameters;
|
|
7717
|
-
if (params && "final_message" in params) {
|
|
7718
|
-
const { final_message: _dropped, ...restParams } = params;
|
|
7719
|
-
void _dropped;
|
|
7720
|
-
mutated = true;
|
|
7721
|
-
return {
|
|
7722
|
-
...tc,
|
|
7723
|
-
arguments: JSON.stringify({ ...args, parameters: restParams }),
|
|
7724
|
-
};
|
|
7725
|
-
}
|
|
7726
|
-
}
|
|
7727
|
-
return tc;
|
|
7728
|
-
});
|
|
7729
|
-
return mutated ? { ...msg, toolCalls: sanitizedToolCalls } : msg;
|
|
7730
|
-
});
|
|
7731
|
-
}
|
|
7732
7614
|
/**
|
|
7733
7615
|
* Run LLM-as-judge quality assessment on a completed agent run.
|
|
7734
7616
|
*/
|
|
@@ -8286,7 +8168,9 @@ class Graphlit {
|
|
|
8286
8168
|
if (!handler) {
|
|
8287
8169
|
throw new Error(`No handler found for tool: ${toolCall.name}`);
|
|
8288
8170
|
}
|
|
8289
|
-
parsedArguments = toolCall.arguments
|
|
8171
|
+
parsedArguments = toolCall.arguments
|
|
8172
|
+
? JSON.parse(toolCall.arguments)
|
|
8173
|
+
: {};
|
|
8290
8174
|
// Add timeout for individual tool calls (30 seconds)
|
|
8291
8175
|
const toolTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error("Tool execution timeout")), 30000));
|
|
8292
8176
|
result = await Promise.race([
|