mirror of
https://github.com/cline/cline.git
synced 2026-09-04 20:02:30 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3700d4387d | |||
| 3ad742f91a | |||
| fc621e42fd | |||
| bb5186979d | |||
| 33e6aed48e | |||
| a70015abb8 | |||
| 528cf6e78a |
+24
-20
@@ -91,7 +91,7 @@ import {
|
||||
ClineTextContentBlock,
|
||||
ClineToolResponseContent,
|
||||
ClineUserContent,
|
||||
} from "@/shared/messages/content"
|
||||
} from "@/shared/messages"
|
||||
import { ShowMessageType } from "@/shared/proto/index.host"
|
||||
import { isClineCliInstalled, isCliSubagentContext } from "@/utils/cli-detector"
|
||||
import { isInTestMode } from "../../services/test/TestMode"
|
||||
@@ -2730,6 +2730,8 @@ export class Task {
|
||||
}]`,
|
||||
},
|
||||
],
|
||||
modelInfo,
|
||||
id: this.streamHandler.requestId,
|
||||
})
|
||||
|
||||
// update api_req_started to have cancelled and cost, so that we can display the cost of the partial stream
|
||||
@@ -2997,26 +2999,9 @@ export class Task {
|
||||
throw new Error("Cline instance aborted")
|
||||
}
|
||||
|
||||
this.taskState.didCompleteReadingStream = true
|
||||
const assistantHasContent = assistantMessage.length > 0 || this.useNativeToolCalls
|
||||
|
||||
// set any blocks to be complete to allow presentAssistantMessage to finish and set userMessageContentReady to true
|
||||
// (could be a text block that had no subsequent tool uses, or a text block at the very end, or an invalid tool use, etc. whatever the case, presentAssistantMessage relies on these blocks either to be completed or the user to reject a block in order to proceed and eventually set userMessageContentReady to true)
|
||||
const partialBlocks = this.taskState.assistantMessageContent.filter((block) => block.partial)
|
||||
partialBlocks.forEach((block) => {
|
||||
block.partial = false
|
||||
})
|
||||
// in case there are native tool calls pending
|
||||
const partialToolBlocks = toolUseHandler.getPartialToolUsesAsContent()?.map((block) => ({ ...block, partial: false }))
|
||||
this.processNativeToolCalls(assistantTextOnly, partialToolBlocks)
|
||||
|
||||
if (partialBlocks.length > 0) {
|
||||
await this.presentAssistantMessage() // if there is content to update then it will complete and update this.userMessageContentReady to true, which we pwaitfor before making the next request. all this is really doing is presenting the last partial message that we just set to complete
|
||||
}
|
||||
|
||||
// now add to apiconversationhistory
|
||||
// need to save assistant responses to file before proceeding to tool use since user can exit at any moment and we wouldn't be able to save the assistant's response
|
||||
let didEndLoop = false
|
||||
if (assistantMessage.length > 0 || this.useNativeToolCalls) {
|
||||
if (assistantHasContent) {
|
||||
const currentMode = this.stateManager.getGlobalSettingsKey("mode")
|
||||
telemetryService.captureConversationTurnEvent(
|
||||
this.ulid,
|
||||
@@ -3084,7 +3069,26 @@ export class Task {
|
||||
id: requestId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.taskState.didCompleteReadingStream = true
|
||||
|
||||
// set any blocks to be complete to allow presentAssistantMessage to finish and set userMessageContentReady to true
|
||||
// (could be a text block that had no subsequent tool uses, or a text block at the very end, or an invalid tool use, etc. whatever the case, presentAssistantMessage relies on these blocks either to be completed or the user to reject a block in order to proceed and eventually set userMessageContentReady to true)
|
||||
const partialBlocks = this.taskState.assistantMessageContent.filter((block) => block.partial)
|
||||
partialBlocks.forEach((block) => {
|
||||
block.partial = false
|
||||
})
|
||||
// in case there are native tool calls pending
|
||||
const partialToolBlocks = toolUseHandler.getPartialToolUsesAsContent()?.map((block) => ({ ...block, partial: false }))
|
||||
this.processNativeToolCalls(assistantTextOnly, partialToolBlocks)
|
||||
|
||||
if (partialBlocks.length > 0) {
|
||||
await this.presentAssistantMessage() // if there is content to update then it will complete and update this.userMessageContentReady to true, which we pwaitfor before making the next request. all this is really doing is presenting the last partial message that we just set to complete
|
||||
}
|
||||
|
||||
let didEndLoop = false
|
||||
if (assistantHasContent) {
|
||||
// NOTE: this comment is here for future reference - this was a workaround for userMessageContent not getting set to true. It was due to it not recursively calling for partial blocks when didRejectTool, so it would get stuck waiting for a partial block to complete before it could continue.
|
||||
// in case the content blocks finished
|
||||
// it may be the api stream finished after the last parsed content block was executed, so we are able to detect out of bounds and set userMessageContentReady to true (note you should not call presentAssistantMessage since if the last block is completed it will be presented again)
|
||||
|
||||
@@ -12,7 +12,7 @@ import { DictationSettings } from "./DictationSettings"
|
||||
import { FocusChainSettings } from "./FocusChainSettings"
|
||||
import { HistoryItem } from "./HistoryItem"
|
||||
import { McpDisplayMode } from "./McpDisplayMode"
|
||||
import { ClineMessageModelInfo } from "./messages/content"
|
||||
import { ClineMessageModelInfo } from "./messages"
|
||||
import { OnboardingModelGroup } from "./proto/cline/state"
|
||||
import { Mode, OpenaiReasoningEffort } from "./storage/types"
|
||||
import { TelemetrySetting } from "./TelemetrySetting"
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"schema_version": "ATIF-v1.3",
|
||||
"session_id": "snapshot-test-session",
|
||||
"agent": {
|
||||
"name": "cline",
|
||||
"version": "1.0.0",
|
||||
"extra": {}
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": 1,
|
||||
"timestamp": "2025-01-15T10:00:00Z",
|
||||
"source": "user",
|
||||
"message": "Fix the authentication bug"
|
||||
},
|
||||
{
|
||||
"step_id": 2,
|
||||
"timestamp": "2025-01-15T10:00:05Z",
|
||||
"source": "agent",
|
||||
"message": "I'll investigate the issue",
|
||||
"model_name": "claude-sonnet-4-20250514",
|
||||
"reasoning_content": "Need to examine the auth code first",
|
||||
"tool_calls": [
|
||||
{
|
||||
"tool_call_id": "call_read_1",
|
||||
"function_name": "Read",
|
||||
"arguments": {
|
||||
"file_path": "/auth/login.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"prompt_tokens": 1000,
|
||||
"completion_tokens": 500,
|
||||
"cached_tokens": 200,
|
||||
"cost_usd": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_id": 3,
|
||||
"timestamp": "2025-01-15T10:00:10Z",
|
||||
"source": "user",
|
||||
"message": ""
|
||||
},
|
||||
{
|
||||
"step_id": 4,
|
||||
"timestamp": "2025-01-15T10:00:15Z",
|
||||
"source": "agent",
|
||||
"message": "Found the issue. The password validation is missing.",
|
||||
"model_name": "claude-sonnet-4-20250514",
|
||||
"tool_calls": [
|
||||
{
|
||||
"tool_call_id": "call_edit_1",
|
||||
"function_name": "Edit",
|
||||
"arguments": {
|
||||
"file_path": "/auth/login.ts",
|
||||
"old_string": "if (username) {",
|
||||
"new_string": "if (username && password) {"
|
||||
}
|
||||
}
|
||||
],
|
||||
"metrics": {
|
||||
"prompt_tokens": 1500,
|
||||
"completion_tokens": 300,
|
||||
"cached_tokens": 400,
|
||||
"cost_usd": 0.03
|
||||
}
|
||||
}
|
||||
],
|
||||
"notes": "Fixed authentication bug",
|
||||
"final_metrics": {
|
||||
"total_prompt_tokens": 2500,
|
||||
"total_completion_tokens": 800,
|
||||
"total_cached_tokens": 600,
|
||||
"total_cost_usd": 0.08,
|
||||
"total_steps": 4,
|
||||
"extra": {}
|
||||
},
|
||||
"extra": {}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is the current trading price of Alphabet (GOOGL)?",
|
||||
"timestamp": "2025-10-11T10:30:00Z"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "thinking",
|
||||
"thinking": "The request requires two data points: the current stock price and the latest volume data. I will execute two simultaneous tool calls—one for price and one for volume—to retrieve this information in a single step.",
|
||||
"signature": "2"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "I will search for the current trading price and volume for GOOGL."
|
||||
},
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "call_price_1",
|
||||
"name": "financial_search",
|
||||
"input": {
|
||||
"ticker": "GOOGL",
|
||||
"metric": "price"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "call_volume_2",
|
||||
"name": "financial_search",
|
||||
"input": {
|
||||
"ticker": "GOOGL",
|
||||
"metric": "volume"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "tool_result",
|
||||
"tool_use_id": "call_price_1",
|
||||
"content": "GOOGL is currently trading at $185.35 (Close: 10/11/2025)"
|
||||
},
|
||||
{
|
||||
"type": "tool_result",
|
||||
"tool_use_id": "call_volume_2",
|
||||
"content": "GOOGL volume: 1.5M shares traded."
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-10-11T10:30:02Z",
|
||||
"modelInfo": {
|
||||
"modelId": "gemini-2.5-flash",
|
||||
"providerId": "unknown",
|
||||
"reasoningEffort": "medium"
|
||||
},
|
||||
"metrics": {
|
||||
"promptTokens": 520,
|
||||
"completionTokens": 80,
|
||||
"cachedTokens": 200,
|
||||
"totalCost": 0.00045
|
||||
}
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{
|
||||
"type": "thinking",
|
||||
"thinking": "The previous step retrieved all necessary data. I will now format this into a final conversational response for the user and terminate the task.",
|
||||
"signature": "3"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "As of October 11, 2025, Alphabet (GOOGL) is trading at $185.35 with a volume of 1.5M shares traded."
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-10-11T10:30:05Z",
|
||||
"modelInfo": {
|
||||
"modelId": "gemini-2.5-flash",
|
||||
"providerId": "unknown",
|
||||
"reasoningEffort": "low"
|
||||
},
|
||||
"metrics": {
|
||||
"promptTokens": 600,
|
||||
"completionTokens": 44,
|
||||
"cachedTokens": 0,
|
||||
"totalCost": 0.00033
|
||||
}
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,457 @@
|
||||
/**
|
||||
* ATIF Conversion Utilities
|
||||
*
|
||||
* Functions for converting between Cline storage format and ATIF trajectory format.
|
||||
*/
|
||||
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import type { ClineApiReqInfo, ClineMessage } from "../ExtensionMessage"
|
||||
import type {
|
||||
ATIF_AGENT_NAME,
|
||||
ATIF_SCHEMA_VERSION,
|
||||
ATIFAgentSchema,
|
||||
ATIFObservationResultSchema,
|
||||
ATIFStepObject,
|
||||
ATIFToolCallSchema,
|
||||
ATIFTrajectory,
|
||||
} from "./atif"
|
||||
import {
|
||||
ClineAssistantThinkingBlock,
|
||||
type ClineAssistantToolUseBlock,
|
||||
type ClineContent,
|
||||
type ClineStorageMessage,
|
||||
type ClineUserToolResultContentBlock,
|
||||
} from "./content"
|
||||
|
||||
/**
|
||||
* Enriches API conversation history messages with metrics from UI messages.
|
||||
* Matches assistant messages to their corresponding api_req_started messages
|
||||
* to populate the metrics field for ATIF export.
|
||||
*
|
||||
* @param apiConversationHistory - The API conversation history messages
|
||||
* @param clineMessages - The UI messages containing api_req_started with metrics
|
||||
* @returns A new array of messages with metrics populated
|
||||
*/
|
||||
export function enrichMessagesWithMetrics(
|
||||
apiConversationHistory: ClineStorageMessage[],
|
||||
clineMessages: ClineMessage[],
|
||||
): ClineStorageMessage[] {
|
||||
// Find all api_req_started messages with their metrics
|
||||
const apiReqMessages = clineMessages
|
||||
.filter((m) => m.type === "say" && m.say === "api_req_started" && m.text)
|
||||
.map((m) => {
|
||||
try {
|
||||
const info: ClineApiReqInfo = JSON.parse(m.text || "{}")
|
||||
return {
|
||||
ts: m.ts,
|
||||
tokensIn: info.tokensIn,
|
||||
tokensOut: info.tokensOut,
|
||||
cacheWrites: info.cacheWrites,
|
||||
cacheReads: info.cacheReads,
|
||||
cost: info.cost,
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter((m) => m !== null)
|
||||
|
||||
// Create a copy of the conversation history and enrich assistant messages
|
||||
let apiReqIndex = 0
|
||||
return apiConversationHistory.map((message) => {
|
||||
if (message.role === "assistant" && apiReqIndex < apiReqMessages.length) {
|
||||
const metrics = apiReqMessages[apiReqIndex]
|
||||
apiReqIndex++
|
||||
|
||||
// Only add metrics if we have valid data
|
||||
if (metrics && typeof metrics.tokensIn === "number" && typeof metrics.tokensOut === "number") {
|
||||
return {
|
||||
...message,
|
||||
metrics: {
|
||||
promptTokens: metrics.tokensIn,
|
||||
completionTokens: metrics.tokensOut,
|
||||
cachedTokens: (metrics.cacheWrites || 0) + (metrics.cacheReads || 0),
|
||||
totalCost: metrics.cost || 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
return message
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for converting Cline messages to ATIF trajectory
|
||||
*/
|
||||
export interface ClineToATIFOptions {
|
||||
sessionId: string
|
||||
agentVersion: string
|
||||
defaultModelName?: string
|
||||
notes?: string
|
||||
agentName?: typeof ATIF_AGENT_NAME
|
||||
schemaVersion?: typeof ATIF_SCHEMA_VERSION
|
||||
/** Optional: UI messages to extract metrics from */
|
||||
clineMessages?: ClineMessage[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of ClineStorageMessages to an ATIF trajectory
|
||||
*/
|
||||
export function convertClineMessagesToATIF(messages: ClineStorageMessage[], options: ClineToATIFOptions): ATIFTrajectory {
|
||||
const agent: ATIFAgentSchema = {
|
||||
name: options.agentName || "cline",
|
||||
version: options.agentVersion,
|
||||
model_name: options.defaultModelName,
|
||||
extra: {},
|
||||
}
|
||||
|
||||
// Enrich messages with metrics if clineMessages are provided
|
||||
const enrichedMessages = options.clineMessages ? enrichMessagesWithMetrics(messages, options.clineMessages) : messages
|
||||
|
||||
// Convert messages to steps
|
||||
const steps: ATIFStepObject[] = []
|
||||
let stepId = 1
|
||||
|
||||
for (const message of enrichedMessages) {
|
||||
const step = convertClineMessageToATIFStep(message, stepId)
|
||||
steps.push(step)
|
||||
stepId++
|
||||
}
|
||||
|
||||
// Calculate final metrics
|
||||
const final_metrics = calculateFinalMetrics(steps)
|
||||
|
||||
return {
|
||||
schema_version: options.schemaVersion || "ATIF-v1.3",
|
||||
session_id: options.sessionId,
|
||||
agent,
|
||||
steps,
|
||||
notes: options.notes,
|
||||
final_metrics,
|
||||
extra: {},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single ClineStorageMessage to an ATIF step
|
||||
*/
|
||||
export function convertClineMessageToATIFStep(message: ClineStorageMessage, stepId: number): ATIFStepObject {
|
||||
// Determine source from role
|
||||
const source = message.role === "assistant" ? "agent" : message.role
|
||||
|
||||
// Extract message text
|
||||
const messageText = extractMessageText(message.content)
|
||||
|
||||
// Extract tool calls (for assistant messages)
|
||||
const tool_calls = message.role === "assistant" ? extractToolCalls(message.content) : undefined
|
||||
|
||||
// Extract observations (from tool results in user messages or from message.observation)
|
||||
// const observation = message.observation || extractObservationFromContent(message.content)
|
||||
|
||||
// Build step object
|
||||
const step: ATIFStepObject = {
|
||||
step_id: stepId,
|
||||
timestamp: message.timestamp || new Date().toISOString(),
|
||||
source,
|
||||
message: messageText,
|
||||
// extra: message.extra,
|
||||
}
|
||||
|
||||
// Add agent-specific fields
|
||||
if (source === "agent") {
|
||||
if (message.modelInfo?.modelId) {
|
||||
step.model_name = message.modelInfo.modelId
|
||||
}
|
||||
if (message.modelInfo?.reasoningEffort !== undefined) {
|
||||
step.reasoning_effort = message.modelInfo.reasoningEffort
|
||||
}
|
||||
if (message.content && Array.isArray(message.content)) {
|
||||
const thinkingBlock = message.content.find((b) => b.type === "thinking")
|
||||
step.reasoning_content = thinkingBlock?.thinking
|
||||
}
|
||||
if (tool_calls && tool_calls.length > 0) {
|
||||
step.tool_calls = tool_calls
|
||||
}
|
||||
if (message.metrics) {
|
||||
step.metrics = {
|
||||
prompt_tokens: message.metrics.promptTokens || 0,
|
||||
completion_tokens: message.metrics.completionTokens || 0,
|
||||
cached_tokens: message.metrics.cachedTokens || 0,
|
||||
cost_usd: message.metrics.totalCost || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Add observation if present
|
||||
// if (observation && observation.results.length > 0) {
|
||||
// step.observation = observation
|
||||
// }
|
||||
|
||||
return step
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts text content from message content
|
||||
*/
|
||||
function extractMessageText(content: string | ClineContent[]): string {
|
||||
if (typeof content === "string") {
|
||||
return content
|
||||
}
|
||||
|
||||
// Find the first text block
|
||||
const textBlock = content.find((block) => block.type === "text")
|
||||
if (textBlock && "text" in textBlock) {
|
||||
return textBlock.text
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts tool calls from assistant message content
|
||||
*/
|
||||
function extractToolCalls(content: string | ClineContent[]): ATIFToolCallSchema[] | undefined {
|
||||
if (typeof content === "string") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const toolCalls: ATIFToolCallSchema[] = []
|
||||
|
||||
for (const block of content) {
|
||||
if (block.type === "tool_use") {
|
||||
const toolUseBlock = block as ClineAssistantToolUseBlock
|
||||
toolCalls.push({
|
||||
tool_call_id: toolUseBlock.id,
|
||||
function_name: toolUseBlock.name,
|
||||
arguments: toolUseBlock.input as Record<string, unknown>,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return toolCalls.length > 0 ? toolCalls : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts observation data from message content (tool results)
|
||||
*/
|
||||
export function extractObservationFromContent(
|
||||
content: string | ClineContent[],
|
||||
): { results: ATIFObservationResultSchema[] } | undefined {
|
||||
if (typeof content === "string") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const results: ATIFObservationResultSchema[] = []
|
||||
|
||||
for (const block of content) {
|
||||
if (block.type === "tool_result") {
|
||||
const toolResultBlock = block as ClineUserToolResultContentBlock
|
||||
const resultContent = toolResultBlock.content ? extractToolResultContent(toolResultBlock.content) : ""
|
||||
|
||||
results.push({
|
||||
source_call_id: toolResultBlock.tool_use_id,
|
||||
content: resultContent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return results.length > 0 ? { results } : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts text content from tool result content
|
||||
*/
|
||||
function extractToolResultContent(content: string | Array<Anthropic.TextBlockParam | Anthropic.ImageBlockParam>): string {
|
||||
if (typeof content === "string") {
|
||||
return content
|
||||
}
|
||||
|
||||
// Concatenate all text blocks
|
||||
const textParts: string[] = []
|
||||
for (const block of content) {
|
||||
if (block.type === "text") {
|
||||
textParts.push(block.text)
|
||||
}
|
||||
}
|
||||
|
||||
return textParts.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates aggregate metrics for the entire trajectory
|
||||
*/
|
||||
function calculateFinalMetrics(steps: ATIFStepObject[]) {
|
||||
let total_prompt_tokens = 0
|
||||
let total_completion_tokens = 0
|
||||
let total_cached_tokens = 0
|
||||
let total_cost_usd = 0
|
||||
|
||||
for (const step of steps) {
|
||||
if (step.metrics) {
|
||||
total_prompt_tokens += step.metrics.prompt_tokens || 0
|
||||
total_completion_tokens += step.metrics.completion_tokens || 0
|
||||
total_cached_tokens += step.metrics.cached_tokens || 0
|
||||
total_cost_usd += step.metrics.cost_usd || 0
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
total_prompt_tokens,
|
||||
total_completion_tokens,
|
||||
total_cached_tokens,
|
||||
total_cost_usd,
|
||||
total_steps: steps.length,
|
||||
extra: {},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ATIF step back to a ClineStorageMessage
|
||||
* Useful for importing ATIF trajectories into Cline format
|
||||
*/
|
||||
export function convertATIFStepToClineMessage(step: ATIFStepObject): ClineStorageMessage {
|
||||
// Determine role from source
|
||||
const role = step.source === "agent" ? "assistant" : "user"
|
||||
|
||||
// Build content array
|
||||
const content: ClineContent[] = []
|
||||
|
||||
// Add main message as text block
|
||||
if (step.message) {
|
||||
content.push({
|
||||
type: "text",
|
||||
text: step.message,
|
||||
})
|
||||
}
|
||||
|
||||
// Add tool calls (for agent steps)
|
||||
if (step.tool_calls) {
|
||||
for (const toolCall of step.tool_calls) {
|
||||
content.push({
|
||||
type: "tool_use",
|
||||
id: toolCall.tool_call_id,
|
||||
name: toolCall.function_name,
|
||||
input: toolCall.arguments,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add tool results (if observation is present)
|
||||
if (step.observation) {
|
||||
for (const result of step.observation.results) {
|
||||
if (result.source_call_id) {
|
||||
content.push({
|
||||
type: "tool_result",
|
||||
tool_use_id: result.source_call_id,
|
||||
content: result.content || "",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the message
|
||||
const message: ClineStorageMessage = {
|
||||
role,
|
||||
content: content.length === 1 && content[0].type === "text" ? content[0].text : content,
|
||||
timestamp: step.timestamp,
|
||||
}
|
||||
|
||||
// Add agent-specific fields
|
||||
if (role === "assistant") {
|
||||
if (!step.model_name) {
|
||||
throw new Error("Model name is required for assistant messages")
|
||||
}
|
||||
|
||||
message.modelInfo = {
|
||||
modelId: step.model_name,
|
||||
providerId: "unknown", // Provider info not in ATIF
|
||||
reasoningEffort: step.reasoning_effort,
|
||||
}
|
||||
|
||||
if (step.reasoning_content) {
|
||||
const thinkingBlock: ClineAssistantThinkingBlock = {
|
||||
type: "thinking",
|
||||
thinking: step.reasoning_content,
|
||||
// TODO: signature should be provided if needed
|
||||
signature: step.step_id.toString(),
|
||||
}
|
||||
if (Array.isArray(message.content)) {
|
||||
message.content.unshift(thinkingBlock)
|
||||
} else {
|
||||
message.content = [thinkingBlock, { type: "text", text: message.content }]
|
||||
}
|
||||
}
|
||||
|
||||
if (step.metrics) {
|
||||
message.metrics = {
|
||||
promptTokens: step.metrics.prompt_tokens || 0,
|
||||
completionTokens: step.metrics.completion_tokens || 0,
|
||||
cachedTokens: step.metrics.cached_tokens || 0,
|
||||
totalCost: step.metrics.cost_usd || 0,
|
||||
}
|
||||
}
|
||||
// message.observation = step.observation
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an entire ATIF trajectory to an array of ClineStorageMessages
|
||||
*/
|
||||
export function convertATIFToClineMessages(trajectory: ATIFTrajectory): ClineStorageMessage[] {
|
||||
return trajectory.steps.map((step) => convertATIFStepToClineMessage(step))
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a message can be converted to ATIF format
|
||||
* Returns validation errors if any
|
||||
*/
|
||||
export function validateClineMessageForATIF(message: ClineStorageMessage): string[] {
|
||||
const errors: string[] = []
|
||||
|
||||
// Check role
|
||||
if (!message.role) {
|
||||
errors.push("Message must have a role")
|
||||
}
|
||||
|
||||
// Check content
|
||||
if (!message.content) {
|
||||
errors.push("Message must have content")
|
||||
}
|
||||
|
||||
// Check agent-specific fields are only on assistant messages
|
||||
if (message.role !== "assistant") {
|
||||
if (message.modelInfo?.reasoningEffort !== undefined) {
|
||||
errors.push("reasoning_effort can only be set on assistant messages")
|
||||
}
|
||||
// Check for reasoning_content in message.content
|
||||
if (
|
||||
Array.isArray(message.content) &&
|
||||
message.content.some((block: any) => block.type === "reasoning_content" || block.reasoning_content !== undefined)
|
||||
) {
|
||||
errors.push("reasoning_content can only be set on assistant messages")
|
||||
} else if (typeof message.content === "object" && message.content !== null && "reasoning_content" in message.content) {
|
||||
errors.push("reasoning_content can only be set on assistant messages")
|
||||
}
|
||||
if (message.metrics) {
|
||||
errors.push("metrics can only be set on assistant messages")
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an ATIF trajectory to JSON string
|
||||
*/
|
||||
export function serializeATIFTrajectory(trajectory: ATIFTrajectory, pretty = true): string {
|
||||
return JSON.stringify(trajectory, null, pretty ? 2 : 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an ATIF trajectory from JSON string
|
||||
*/
|
||||
export function parseATIFTrajectory(json: string): ATIFTrajectory {
|
||||
return JSON.parse(json) as ATIFTrajectory
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Agent Trajectory Interchange Format (ATIF) v1.3 Type Definitions
|
||||
*
|
||||
* This file contains TypeScript type definitions for the ATIF specification,
|
||||
* which provides a standardized format for logging LLM agent interactions.
|
||||
*
|
||||
* Specification: https://github.com/harbor-project/rfcs/blob/main/0001-trajectory-format.md
|
||||
*/
|
||||
|
||||
/**
|
||||
* Root-level ATIF trajectory object
|
||||
*/
|
||||
export interface ATIFTrajectory {
|
||||
schema_version: string // e.g., "ATIF-v1.3"
|
||||
session_id: string
|
||||
agent: ATIFAgentSchema
|
||||
steps: ATIFStepObject[]
|
||||
notes?: string
|
||||
final_metrics?: ATIFFinalMetricsSchema
|
||||
extra?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent configuration schema
|
||||
*/
|
||||
export interface ATIFAgentSchema {
|
||||
name: string // e.g., "cline"
|
||||
version: string // e.g., "1.0.0"
|
||||
model_name?: string // Default model for trajectory
|
||||
extra?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate metrics for entire trajectory
|
||||
*/
|
||||
export interface ATIFFinalMetricsSchema {
|
||||
total_prompt_tokens?: number
|
||||
total_completion_tokens?: number
|
||||
total_cached_tokens?: number
|
||||
total_cost_usd?: number
|
||||
total_steps?: number
|
||||
extra?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual step in the trajectory
|
||||
*/
|
||||
export interface ATIFStepObject {
|
||||
step_id: number // Ordinal index starting from 1
|
||||
timestamp?: string // ISO 8601 timestamp
|
||||
source: "system" | "user" | "agent"
|
||||
model_name?: string // Only applicable when source is "agent"
|
||||
reasoning_effort?: string | number // Only applicable when source is "agent"
|
||||
message: string // Required but can be empty string
|
||||
reasoning_content?: string // Only applicable when source is "agent"
|
||||
tool_calls?: ATIFToolCallSchema[] // Only applicable when source is "agent"
|
||||
observation?: ATIFObservationSchema // Can be present for agent and system steps
|
||||
metrics?: ATIFMetricsSchema // Only applicable when source is "agent"
|
||||
extra?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool call schema (function invocation)
|
||||
*/
|
||||
export interface ATIFToolCallSchema {
|
||||
tool_call_id: string
|
||||
function_name: string
|
||||
arguments: Record<string, unknown> // Must be valid JSON object, can be empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Observation schema (environment feedback)
|
||||
*/
|
||||
export interface ATIFObservationSchema {
|
||||
results: ATIFObservationResultSchema[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual observation result
|
||||
*/
|
||||
export interface ATIFObservationResultSchema {
|
||||
source_call_id?: string // Maps to tool_call_id, null for non-tool actions
|
||||
content?: string // May be omitted when subagent_trajectory_ref is present
|
||||
subagent_trajectory_ref?: ATIFSubagentTrajectoryRefSchema[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Subagent trajectory reference
|
||||
*/
|
||||
export interface ATIFSubagentTrajectoryRefSchema {
|
||||
session_id: string
|
||||
trajectory_path?: string // File path, URL, or database reference
|
||||
extra?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-step LLM metrics
|
||||
*/
|
||||
export interface ATIFMetricsSchema {
|
||||
prompt_tokens?: number // Total input tokens (includes cached + non-cached)
|
||||
completion_tokens?: number // Total output tokens
|
||||
cached_tokens?: number // Subset of prompt_tokens that were cache hits
|
||||
cost_usd?: number // Monetary cost for this step
|
||||
completion_token_ids?: number[] // Token IDs for RL training (v1.3)
|
||||
logprobs?: number[] // Log probabilities for each completion token
|
||||
extra?: Record<string, unknown> // Provider-specific metrics
|
||||
}
|
||||
|
||||
/**
|
||||
* Source type for ATIF steps
|
||||
*/
|
||||
export type ATIFSourceType = "system" | "user" | "agent"
|
||||
|
||||
/**
|
||||
* Mapping between Cline roles and ATIF sources
|
||||
*/
|
||||
export const CLINE_ROLE_TO_ATIF_SOURCE: Record<string, ATIFSourceType> = {
|
||||
user: "user",
|
||||
assistant: "agent",
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants for ATIF format
|
||||
*/
|
||||
export const ATIF_SCHEMA_VERSION = "ATIF-v1.3"
|
||||
export const ATIF_AGENT_NAME = "cline"
|
||||
@@ -1,13 +1,9 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ClineMessageModelInfo, ClineTokenMetrics } from "./metrics"
|
||||
|
||||
type ClinePromptInputContent = string
|
||||
export type ClinePromptInputContent = string
|
||||
|
||||
type ClineMessageRole = "user" | "assistant"
|
||||
|
||||
export interface ClineMessageModelInfo {
|
||||
modelId: string
|
||||
providerId: string
|
||||
}
|
||||
export type ClineMessageRole = "user" | "assistant"
|
||||
|
||||
export interface ClineReasoningDetailParam {
|
||||
type: "reasoning.text" | string
|
||||
@@ -83,6 +79,8 @@ export type ClineContent = ClineUserContent | ClineAssistantContent
|
||||
* This ensures backward compatibility where the messages were stored in Anthropic format,
|
||||
* while allowing for additional metadata specific to Cline to avoid unknown fields in Anthropic SDK
|
||||
* added by ignoring the type checking for those fields.
|
||||
*
|
||||
* Extended with ATIF (Agent Trajectory Interchange Format) v1.3 compatible fields.
|
||||
*/
|
||||
export interface ClineStorageMessage extends Anthropic.MessageParam {
|
||||
/**
|
||||
@@ -97,6 +95,16 @@ export interface ClineStorageMessage extends Anthropic.MessageParam {
|
||||
* MUST be removed before sending message to any LLM provider.
|
||||
*/
|
||||
modelInfo?: ClineMessageModelInfo
|
||||
/**
|
||||
* ISO 8601 timestamp indicating when this message occurred
|
||||
* e.g., "2025-10-16T14:30:00Z"
|
||||
*/
|
||||
timestamp?: string
|
||||
/**
|
||||
* LLM operational and performance metrics for this message
|
||||
* Includes token counts, costs.
|
||||
*/
|
||||
metrics?: ClineTokenMetrics
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Messages Module - Exports for Cline message types and ATIF trajectory support
|
||||
*/
|
||||
// ATIF types
|
||||
export type {
|
||||
ATIFAgentSchema,
|
||||
ATIFFinalMetricsSchema,
|
||||
ATIFMetricsSchema,
|
||||
ATIFObservationResultSchema,
|
||||
ATIFObservationSchema,
|
||||
ATIFSourceType,
|
||||
ATIFStepObject,
|
||||
ATIFSubagentTrajectoryRefSchema,
|
||||
ATIFToolCallSchema,
|
||||
ATIFTrajectory,
|
||||
} from "./atif"
|
||||
export { ATIF_AGENT_NAME, ATIF_SCHEMA_VERSION, CLINE_ROLE_TO_ATIF_SOURCE } from "./atif"
|
||||
// ATIF conversion utilities
|
||||
export type { ClineToATIFOptions } from "./atif-converter"
|
||||
export {
|
||||
convertATIFStepToClineMessage,
|
||||
convertATIFToClineMessages,
|
||||
convertClineMessagesToATIF,
|
||||
convertClineMessageToATIFStep,
|
||||
parseATIFTrajectory,
|
||||
serializeATIFTrajectory,
|
||||
validateClineMessageForATIF,
|
||||
} from "./atif-converter"
|
||||
// Core content types
|
||||
export type {
|
||||
ClineAssistantContent,
|
||||
ClineAssistantRedactedThinkingBlock,
|
||||
ClineAssistantThinkingBlock,
|
||||
ClineAssistantToolUseBlock,
|
||||
ClineContent,
|
||||
ClineDocumentContentBlock,
|
||||
ClineImageContentBlock,
|
||||
ClineMessageRole,
|
||||
ClinePromptInputContent,
|
||||
ClineReasoningDetailParam,
|
||||
ClineStorageMessage,
|
||||
ClineTextContentBlock,
|
||||
ClineToolResponseContent,
|
||||
ClineUserContent,
|
||||
ClineUserToolResultContentBlock,
|
||||
} from "./content"
|
||||
export { cleanContentBlock, convertClineStorageToAnthropicMessage, REASONING_DETAILS_PROVIDERS } from "./content"
|
||||
export type { ClineMessageModelInfo, ClineTokenMetrics } from "./metrics"
|
||||
// Trajectory management
|
||||
export {
|
||||
createTrajectoryBuilder,
|
||||
readTrajectory,
|
||||
readTrajectoryFromJSON,
|
||||
TrajectoryBuilder,
|
||||
TrajectoryReader,
|
||||
TrajectoryUpdater,
|
||||
updateTrajectory,
|
||||
} from "./trajectory-manager"
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface ClineMessageModelInfo {
|
||||
modelId: string
|
||||
providerId: string
|
||||
temperature?: number
|
||||
maxTokens?: number
|
||||
reasoningEffort?: string | number
|
||||
}
|
||||
|
||||
export interface ClineTokenMetrics {
|
||||
promptTokens: number // Total input tokens (includes cached + non-cached)
|
||||
completionTokens: number // Total output tokens
|
||||
cachedTokens: number // Subset of prompt_tokens that were cache hits
|
||||
totalCost: number // Monetary cost for this step
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* Trajectory Manager
|
||||
*
|
||||
* High-level utilities for managing ATIF trajectories in Cline.
|
||||
* Provides functions for creating, updating, and exporting trajectories.
|
||||
*/
|
||||
|
||||
import type { ATIFMetricsSchema, ATIFObservationSchema, ATIFStepObject, ATIFTrajectory } from "./atif"
|
||||
import {
|
||||
type ClineToATIFOptions,
|
||||
convertATIFToClineMessages,
|
||||
convertClineMessagesToATIF,
|
||||
parseATIFTrajectory,
|
||||
serializeATIFTrajectory,
|
||||
} from "./atif-converter"
|
||||
import type { ClineStorageMessage } from "./content"
|
||||
|
||||
/**
|
||||
* TrajectoryBuilder - Fluent API for building ATIF trajectories
|
||||
*/
|
||||
export class TrajectoryBuilder {
|
||||
private messages: ClineStorageMessage[] = []
|
||||
private sessionId: string
|
||||
private agentVersion: string
|
||||
private defaultModelName?: string
|
||||
private notes?: string
|
||||
|
||||
constructor(sessionId: string, agentVersion: string) {
|
||||
this.sessionId = sessionId
|
||||
this.agentVersion = agentVersion
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default model name for the trajectory
|
||||
*/
|
||||
setDefaultModel(modelName: string): this {
|
||||
this.defaultModelName = modelName
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notes to the trajectory
|
||||
*/
|
||||
setNotes(notes: string): this {
|
||||
this.notes = notes
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a message to the trajectory
|
||||
*/
|
||||
addMessage(message: ClineStorageMessage): this {
|
||||
this.messages.push(message)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple messages to the trajectory
|
||||
*/
|
||||
addMessages(messages: ClineStorageMessage[]): this {
|
||||
this.messages.push(...messages)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the ATIF trajectory
|
||||
*/
|
||||
build(): ATIFTrajectory {
|
||||
const options: ClineToATIFOptions = {
|
||||
sessionId: this.sessionId,
|
||||
agentVersion: this.agentVersion,
|
||||
defaultModelName: this.defaultModelName,
|
||||
notes: this.notes,
|
||||
}
|
||||
|
||||
return convertClineMessagesToATIF(this.messages, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and serialize the trajectory to JSON
|
||||
*/
|
||||
buildJSON(pretty = true): string {
|
||||
return serializeATIFTrajectory(this.build(), pretty)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TrajectoryUpdater - Utilities for updating existing trajectories
|
||||
*/
|
||||
export class TrajectoryUpdater {
|
||||
private trajectory: ATIFTrajectory
|
||||
|
||||
constructor(trajectory: ATIFTrajectory) {
|
||||
this.trajectory = trajectory
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new step to the trajectory
|
||||
*/
|
||||
addStep(step: ATIFStepObject): this {
|
||||
// Ensure step_id is sequential
|
||||
const lastStepId = this.trajectory.steps[this.trajectory.steps.length - 1]?.step_id || 0
|
||||
const newStep = { ...step, step_id: lastStepId + 1 }
|
||||
this.trajectory.steps.push(newStep)
|
||||
this.updateFinalMetrics()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Update metrics for a specific step
|
||||
*/
|
||||
updateStepMetrics(stepId: number, metrics: ATIFMetricsSchema): this {
|
||||
const step = this.trajectory.steps.find((s) => s.step_id === stepId)
|
||||
if (step) {
|
||||
step.metrics = { ...step.metrics, ...metrics }
|
||||
this.updateFinalMetrics()
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Add observation to a specific step
|
||||
*/
|
||||
addStepObservation(stepId: number, observation: ATIFObservationSchema): this {
|
||||
const step = this.trajectory.steps.find((s) => s.step_id === stepId)
|
||||
if (step) {
|
||||
step.observation = observation
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the trajectory notes
|
||||
*/
|
||||
setNotes(notes: string): this {
|
||||
this.trajectory.notes = notes
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate and update final metrics
|
||||
*/
|
||||
private updateFinalMetrics(): void {
|
||||
let total_prompt_tokens = 0
|
||||
let total_completion_tokens = 0
|
||||
let total_cached_tokens = 0
|
||||
let total_cost_usd = 0
|
||||
|
||||
for (const step of this.trajectory.steps) {
|
||||
if (step.metrics) {
|
||||
total_prompt_tokens += step.metrics.prompt_tokens || 0
|
||||
total_completion_tokens += step.metrics.completion_tokens || 0
|
||||
total_cached_tokens += step.metrics.cached_tokens || 0
|
||||
total_cost_usd += step.metrics.cost_usd || 0
|
||||
}
|
||||
}
|
||||
|
||||
this.trajectory.final_metrics = {
|
||||
total_prompt_tokens,
|
||||
total_completion_tokens,
|
||||
total_cached_tokens,
|
||||
total_cost_usd,
|
||||
total_steps: this.trajectory.steps.length,
|
||||
extra: this.trajectory.final_metrics?.extra || {},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the updated trajectory
|
||||
*/
|
||||
getTrajectory(): ATIFTrajectory {
|
||||
return this.trajectory
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the trajectory to JSON
|
||||
*/
|
||||
toJSON(pretty = true): string {
|
||||
return serializeATIFTrajectory(this.trajectory, pretty)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TrajectoryReader - Utilities for reading and querying trajectories
|
||||
*/
|
||||
export class TrajectoryReader {
|
||||
private trajectory: ATIFTrajectory
|
||||
|
||||
constructor(trajectory: ATIFTrajectory) {
|
||||
this.trajectory = trajectory
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a trajectory from JSON string
|
||||
*/
|
||||
static fromJSON(json: string): TrajectoryReader {
|
||||
return new TrajectoryReader(parseATIFTrajectory(json))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw trajectory object
|
||||
*/
|
||||
getTrajectory(): ATIFTrajectory {
|
||||
return this.trajectory
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all steps in the trajectory
|
||||
*/
|
||||
getSteps(): ATIFStepObject[] {
|
||||
return this.trajectory.steps
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific step by ID
|
||||
*/
|
||||
getStep(stepId: number): ATIFStepObject | undefined {
|
||||
return this.trajectory.steps.find((s) => s.step_id === stepId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all steps from a specific source (system/user/agent)
|
||||
*/
|
||||
getStepsBySource(source: "system" | "user" | "agent"): ATIFStepObject[] {
|
||||
return this.trajectory.steps.filter((s) => s.source === source)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all agent steps (assistant responses)
|
||||
*/
|
||||
getAgentSteps(): ATIFStepObject[] {
|
||||
return this.getStepsBySource("agent")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user steps
|
||||
*/
|
||||
getUserSteps(): ATIFStepObject[] {
|
||||
return this.getStepsBySource("user")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all system steps
|
||||
*/
|
||||
getSystemSteps(): ATIFStepObject[] {
|
||||
return this.getStepsBySource("system")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get steps that contain tool calls
|
||||
*/
|
||||
getStepsWithToolCalls(): ATIFStepObject[] {
|
||||
return this.trajectory.steps.filter((s) => s.tool_calls && s.tool_calls.length > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get steps that contain observations
|
||||
*/
|
||||
getStepsWithObservations(): ATIFStepObject[] {
|
||||
return this.trajectory.steps.filter((s) => s.observation && s.observation.results.length > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total cost of the trajectory
|
||||
*/
|
||||
getTotalCost(): number {
|
||||
return this.trajectory.final_metrics?.total_cost_usd || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of tokens (prompt + completion)
|
||||
*/
|
||||
getTotalTokens(): number {
|
||||
const prompt = this.trajectory.final_metrics?.total_prompt_tokens || 0
|
||||
const completion = this.trajectory.final_metrics?.total_completion_tokens || 0
|
||||
return prompt + completion
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token statistics
|
||||
*/
|
||||
getTokenStats(): {
|
||||
prompt: number
|
||||
completion: number
|
||||
cached: number
|
||||
total: number
|
||||
} {
|
||||
return {
|
||||
prompt: this.trajectory.final_metrics?.total_prompt_tokens || 0,
|
||||
completion: this.trajectory.final_metrics?.total_completion_tokens || 0,
|
||||
cached: this.trajectory.final_metrics?.total_cached_tokens || 0,
|
||||
total: this.getTotalTokens(),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert trajectory to Cline messages
|
||||
*/
|
||||
toClineMessages(): ClineStorageMessage[] {
|
||||
return convertATIFToClineMessages(this.trajectory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of the trajectory
|
||||
*/
|
||||
getSummary(): {
|
||||
sessionId: string
|
||||
agentName: string
|
||||
agentVersion: string
|
||||
totalSteps: number
|
||||
userSteps: number
|
||||
agentSteps: number
|
||||
systemSteps: number
|
||||
toolCallCount: number
|
||||
totalCost: number
|
||||
totalTokens: number
|
||||
} {
|
||||
const agentSteps = this.getAgentSteps()
|
||||
const toolCallCount = agentSteps.reduce((count, step) => {
|
||||
return count + (step.tool_calls?.length || 0)
|
||||
}, 0)
|
||||
|
||||
return {
|
||||
sessionId: this.trajectory.session_id,
|
||||
agentName: this.trajectory.agent.name,
|
||||
agentVersion: this.trajectory.agent.version,
|
||||
totalSteps: this.trajectory.steps.length,
|
||||
userSteps: this.getUserSteps().length,
|
||||
agentSteps: agentSteps.length,
|
||||
systemSteps: this.getSystemSteps().length,
|
||||
toolCallCount,
|
||||
totalCost: this.getTotalCost(),
|
||||
totalTokens: this.getTotalTokens(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a new trajectory builder
|
||||
*/
|
||||
export function createTrajectoryBuilder(sessionId: string, agentVersion: string): TrajectoryBuilder {
|
||||
return new TrajectoryBuilder(sessionId, agentVersion)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to update an existing trajectory
|
||||
*/
|
||||
export function updateTrajectory(trajectory: ATIFTrajectory): TrajectoryUpdater {
|
||||
return new TrajectoryUpdater(trajectory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to read a trajectory
|
||||
*/
|
||||
export function readTrajectory(trajectory: ATIFTrajectory): TrajectoryReader {
|
||||
return new TrajectoryReader(trajectory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to read a trajectory from JSON
|
||||
*/
|
||||
export function readTrajectoryFromJSON(json: string): TrajectoryReader {
|
||||
return TrajectoryReader.fromJSON(json)
|
||||
}
|
||||
Reference in New Issue
Block a user