claude-code-rust 0.3.0 → 0.4.1
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/README.md +8 -20
- package/agent-sdk/README.md +13 -0
- package/agent-sdk/dist/bridge/auth.js +7 -0
- package/agent-sdk/dist/bridge/commands.js +163 -0
- package/agent-sdk/dist/bridge/history.js +344 -0
- package/agent-sdk/dist/bridge/permissions.js +99 -0
- package/agent-sdk/dist/bridge/shared.js +6 -0
- package/agent-sdk/dist/bridge/tooling.js +290 -0
- package/agent-sdk/dist/bridge/usage.js +95 -0
- package/agent-sdk/dist/bridge.js +1224 -0
- package/agent-sdk/dist/bridge.test.js +445 -0
- package/agent-sdk/dist/types.js +1 -0
- package/package.json +7 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const SESSION_PERMISSION_DESTINATIONS = new Set(["session", "cliArg"]);
|
|
2
|
+
const PERSISTENT_PERMISSION_DESTINATIONS = new Set(["userSettings", "projectSettings", "localSettings"]);
|
|
3
|
+
function formatPermissionRule(rule) {
|
|
4
|
+
return rule.ruleContent === undefined ? rule.toolName : `${rule.toolName}(${rule.ruleContent})`;
|
|
5
|
+
}
|
|
6
|
+
export function formatPermissionUpdates(updates) {
|
|
7
|
+
if (!updates || updates.length === 0) {
|
|
8
|
+
return "<none>";
|
|
9
|
+
}
|
|
10
|
+
return updates
|
|
11
|
+
.map((update) => {
|
|
12
|
+
if (update.type === "addRules" || update.type === "replaceRules" || update.type === "removeRules") {
|
|
13
|
+
const rules = update.rules.map((rule) => formatPermissionRule(rule)).join(", ");
|
|
14
|
+
return `${update.type}:${update.behavior}:${update.destination}=[${rules}]`;
|
|
15
|
+
}
|
|
16
|
+
if (update.type === "setMode") {
|
|
17
|
+
return `${update.type}:${update.mode}:${update.destination}`;
|
|
18
|
+
}
|
|
19
|
+
return `${update.type}:${update.destination}=[${update.directories.join(", ")}]`;
|
|
20
|
+
})
|
|
21
|
+
.join(" | ");
|
|
22
|
+
}
|
|
23
|
+
function splitPermissionSuggestionsByScope(suggestions) {
|
|
24
|
+
if (!suggestions || suggestions.length === 0) {
|
|
25
|
+
return { session: [], persistent: [] };
|
|
26
|
+
}
|
|
27
|
+
const session = [];
|
|
28
|
+
const persistent = [];
|
|
29
|
+
for (const suggestion of suggestions) {
|
|
30
|
+
if (SESSION_PERMISSION_DESTINATIONS.has(suggestion.destination)) {
|
|
31
|
+
session.push(suggestion);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (PERSISTENT_PERMISSION_DESTINATIONS.has(suggestion.destination)) {
|
|
35
|
+
persistent.push(suggestion);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
session.push(suggestion);
|
|
39
|
+
}
|
|
40
|
+
return { session, persistent };
|
|
41
|
+
}
|
|
42
|
+
export function permissionOptionsFromSuggestions(suggestions) {
|
|
43
|
+
const scoped = splitPermissionSuggestionsByScope(suggestions);
|
|
44
|
+
const hasSessionScoped = scoped.session.length > 0;
|
|
45
|
+
const hasPersistentScoped = scoped.persistent.length > 0;
|
|
46
|
+
const sessionOnly = hasSessionScoped && !hasPersistentScoped;
|
|
47
|
+
const options = [{ option_id: "allow_once", name: "Allow once", kind: "allow_once" }];
|
|
48
|
+
options.push({
|
|
49
|
+
option_id: sessionOnly ? "allow_session" : "allow_always",
|
|
50
|
+
name: sessionOnly ? "Allow for session" : "Always allow",
|
|
51
|
+
kind: sessionOnly ? "allow_session" : "allow_always",
|
|
52
|
+
});
|
|
53
|
+
options.push({ option_id: "reject_once", name: "Deny", kind: "reject_once" });
|
|
54
|
+
return options;
|
|
55
|
+
}
|
|
56
|
+
export function permissionResultFromOutcome(outcome, toolCallId, inputData, suggestions, toolName) {
|
|
57
|
+
const scopedSuggestions = splitPermissionSuggestionsByScope(suggestions);
|
|
58
|
+
if (outcome.outcome === "selected") {
|
|
59
|
+
if (outcome.option_id === "allow_once") {
|
|
60
|
+
return { behavior: "allow", updatedInput: inputData, toolUseID: toolCallId };
|
|
61
|
+
}
|
|
62
|
+
if (outcome.option_id === "allow_session") {
|
|
63
|
+
const sessionSuggestions = scopedSuggestions.session;
|
|
64
|
+
const fallbackSuggestions = sessionSuggestions.length > 0
|
|
65
|
+
? sessionSuggestions
|
|
66
|
+
: toolName
|
|
67
|
+
? [
|
|
68
|
+
{
|
|
69
|
+
type: "addRules",
|
|
70
|
+
rules: [{ toolName }],
|
|
71
|
+
behavior: "allow",
|
|
72
|
+
destination: "session",
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
: undefined;
|
|
76
|
+
return {
|
|
77
|
+
behavior: "allow",
|
|
78
|
+
updatedInput: inputData,
|
|
79
|
+
...(fallbackSuggestions && fallbackSuggestions.length > 0
|
|
80
|
+
? { updatedPermissions: fallbackSuggestions }
|
|
81
|
+
: {}),
|
|
82
|
+
toolUseID: toolCallId,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (outcome.option_id === "allow_always") {
|
|
86
|
+
const suggestionsForAlways = scopedSuggestions.persistent;
|
|
87
|
+
return {
|
|
88
|
+
behavior: "allow",
|
|
89
|
+
updatedInput: inputData,
|
|
90
|
+
...(suggestionsForAlways && suggestionsForAlways.length > 0
|
|
91
|
+
? { updatedPermissions: suggestionsForAlways }
|
|
92
|
+
: {}),
|
|
93
|
+
toolUseID: toolCallId,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { behavior: "deny", message: "Permission denied", toolUseID: toolCallId };
|
|
97
|
+
}
|
|
98
|
+
return { behavior: "deny", message: "Permission cancelled", toolUseID: toolCallId };
|
|
99
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { asRecordOrNull } from "./shared.js";
|
|
2
|
+
export const TOOL_RESULT_TYPES = new Set([
|
|
3
|
+
"tool_result",
|
|
4
|
+
"tool_search_tool_result",
|
|
5
|
+
"web_fetch_tool_result",
|
|
6
|
+
"web_search_tool_result",
|
|
7
|
+
"code_execution_tool_result",
|
|
8
|
+
"bash_code_execution_tool_result",
|
|
9
|
+
"text_editor_code_execution_tool_result",
|
|
10
|
+
"mcp_tool_result",
|
|
11
|
+
]);
|
|
12
|
+
export function isToolUseBlockType(blockType) {
|
|
13
|
+
return blockType === "tool_use" || blockType === "server_tool_use" || blockType === "mcp_tool_use";
|
|
14
|
+
}
|
|
15
|
+
export function normalizeToolKind(name) {
|
|
16
|
+
switch (name) {
|
|
17
|
+
case "Bash":
|
|
18
|
+
return "execute";
|
|
19
|
+
case "Read":
|
|
20
|
+
return "read";
|
|
21
|
+
case "Write":
|
|
22
|
+
case "Edit":
|
|
23
|
+
return "edit";
|
|
24
|
+
case "Delete":
|
|
25
|
+
return "delete";
|
|
26
|
+
case "Move":
|
|
27
|
+
return "move";
|
|
28
|
+
case "Glob":
|
|
29
|
+
case "Grep":
|
|
30
|
+
return "search";
|
|
31
|
+
case "WebFetch":
|
|
32
|
+
return "fetch";
|
|
33
|
+
case "TodoWrite":
|
|
34
|
+
return "other";
|
|
35
|
+
case "Task":
|
|
36
|
+
return "think";
|
|
37
|
+
case "ExitPlanMode":
|
|
38
|
+
return "switch_mode";
|
|
39
|
+
default:
|
|
40
|
+
return "think";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function toolTitle(name, input) {
|
|
44
|
+
if (name === "Bash") {
|
|
45
|
+
const command = typeof input.command === "string" ? input.command : "";
|
|
46
|
+
return command || "Terminal";
|
|
47
|
+
}
|
|
48
|
+
if (name === "Glob") {
|
|
49
|
+
const pattern = typeof input.pattern === "string" ? input.pattern : "";
|
|
50
|
+
const path = typeof input.path === "string" ? input.path : "";
|
|
51
|
+
if (pattern && path) {
|
|
52
|
+
return `Glob ${pattern} in ${path}`;
|
|
53
|
+
}
|
|
54
|
+
if (pattern) {
|
|
55
|
+
return `Glob ${pattern}`;
|
|
56
|
+
}
|
|
57
|
+
if (path) {
|
|
58
|
+
return `Glob ${path}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (name === "WebFetch") {
|
|
62
|
+
const url = typeof input.url === "string" ? input.url : "";
|
|
63
|
+
if (url) {
|
|
64
|
+
return `WebFetch ${url}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (name === "WebSearch") {
|
|
68
|
+
const query = typeof input.query === "string" ? input.query : "";
|
|
69
|
+
if (query) {
|
|
70
|
+
return `WebSearch ${query}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if ((name === "Read" || name === "Write" || name === "Edit") && typeof input.file_path === "string") {
|
|
74
|
+
return `${name} ${input.file_path}`;
|
|
75
|
+
}
|
|
76
|
+
return name;
|
|
77
|
+
}
|
|
78
|
+
function editDiffContent(name, input) {
|
|
79
|
+
const filePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
80
|
+
if (!filePath) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
if (name === "Edit") {
|
|
84
|
+
const oldText = typeof input.old_string === "string" ? input.old_string : "";
|
|
85
|
+
const newText = typeof input.new_string === "string" ? input.new_string : "";
|
|
86
|
+
if (!oldText && !newText) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
return [{ type: "diff", old_path: filePath, new_path: filePath, old: oldText, new: newText }];
|
|
90
|
+
}
|
|
91
|
+
if (name === "Write") {
|
|
92
|
+
const newText = typeof input.content === "string" ? input.content : "";
|
|
93
|
+
if (!newText) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
return [{ type: "diff", old_path: filePath, new_path: filePath, old: "", new: newText }];
|
|
97
|
+
}
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
export function createToolCall(toolUseId, name, input) {
|
|
101
|
+
return {
|
|
102
|
+
tool_call_id: toolUseId,
|
|
103
|
+
title: toolTitle(name, input),
|
|
104
|
+
kind: normalizeToolKind(name),
|
|
105
|
+
status: "pending",
|
|
106
|
+
content: editDiffContent(name, input),
|
|
107
|
+
raw_input: input,
|
|
108
|
+
locations: typeof input.file_path === "string" ? [{ path: input.file_path }] : [],
|
|
109
|
+
meta: {
|
|
110
|
+
claudeCode: {
|
|
111
|
+
toolName: name,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function extractText(value) {
|
|
117
|
+
if (typeof value === "string") {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
return value
|
|
122
|
+
.map((entry) => {
|
|
123
|
+
if (typeof entry === "string") {
|
|
124
|
+
return entry;
|
|
125
|
+
}
|
|
126
|
+
if (entry && typeof entry === "object" && "text" in entry && typeof entry.text === "string") {
|
|
127
|
+
return entry.text;
|
|
128
|
+
}
|
|
129
|
+
return "";
|
|
130
|
+
})
|
|
131
|
+
.filter((part) => part.length > 0)
|
|
132
|
+
.join("\n");
|
|
133
|
+
}
|
|
134
|
+
if (value && typeof value === "object" && "text" in value && typeof value.text === "string") {
|
|
135
|
+
return value.text;
|
|
136
|
+
}
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
const PERSISTED_OUTPUT_OPEN_TAG = "<persisted-output>";
|
|
140
|
+
const PERSISTED_OUTPUT_CLOSE_TAG = "</persisted-output>";
|
|
141
|
+
function extractPersistedOutputInnerText(text) {
|
|
142
|
+
const lower = text.toLowerCase();
|
|
143
|
+
const openIdx = lower.indexOf(PERSISTED_OUTPUT_OPEN_TAG);
|
|
144
|
+
if (openIdx < 0) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const bodyStart = openIdx + PERSISTED_OUTPUT_OPEN_TAG.length;
|
|
148
|
+
const closeIdx = lower.indexOf(PERSISTED_OUTPUT_CLOSE_TAG, bodyStart);
|
|
149
|
+
if (closeIdx < 0) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
return text.slice(bodyStart, closeIdx);
|
|
153
|
+
}
|
|
154
|
+
function persistedOutputFirstLine(text) {
|
|
155
|
+
const inner = extractPersistedOutputInnerText(text);
|
|
156
|
+
if (inner === null) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
for (const line of inner.split(/\r?\n/)) {
|
|
160
|
+
const cleaned = line.replace(/^[\s|│┃║]+/u, "").trim();
|
|
161
|
+
if (cleaned.length > 0) {
|
|
162
|
+
return cleaned;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
export function normalizeToolResultText(value) {
|
|
168
|
+
const text = extractText(value);
|
|
169
|
+
if (!text) {
|
|
170
|
+
return "";
|
|
171
|
+
}
|
|
172
|
+
const persistedLine = persistedOutputFirstLine(text);
|
|
173
|
+
if (persistedLine) {
|
|
174
|
+
return persistedLine;
|
|
175
|
+
}
|
|
176
|
+
return text;
|
|
177
|
+
}
|
|
178
|
+
function resolveToolName(toolCall) {
|
|
179
|
+
const meta = asRecordOrNull(toolCall?.meta);
|
|
180
|
+
const claudeCode = asRecordOrNull(meta?.claudeCode);
|
|
181
|
+
const toolName = claudeCode?.toolName;
|
|
182
|
+
return typeof toolName === "string" ? toolName : "";
|
|
183
|
+
}
|
|
184
|
+
function writeDiffFromInput(rawInput) {
|
|
185
|
+
const input = asRecordOrNull(rawInput);
|
|
186
|
+
if (!input) {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
const filePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
190
|
+
const content = typeof input.content === "string" ? input.content : "";
|
|
191
|
+
if (!filePath || !content) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
return [{ type: "diff", old_path: filePath, new_path: filePath, old: "", new: content }];
|
|
195
|
+
}
|
|
196
|
+
function editDiffFromInput(rawInput) {
|
|
197
|
+
const input = asRecordOrNull(rawInput);
|
|
198
|
+
if (!input) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
const filePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
202
|
+
const oldText = typeof input.old_string === "string"
|
|
203
|
+
? input.old_string
|
|
204
|
+
: typeof input.oldString === "string"
|
|
205
|
+
? input.oldString
|
|
206
|
+
: "";
|
|
207
|
+
const newText = typeof input.new_string === "string"
|
|
208
|
+
? input.new_string
|
|
209
|
+
: typeof input.newString === "string"
|
|
210
|
+
? input.newString
|
|
211
|
+
: "";
|
|
212
|
+
if (!filePath || (!oldText && !newText)) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
return [{ type: "diff", old_path: filePath, new_path: filePath, old: oldText, new: newText }];
|
|
216
|
+
}
|
|
217
|
+
function writeDiffFromResult(rawContent) {
|
|
218
|
+
const candidates = Array.isArray(rawContent) ? rawContent : [rawContent];
|
|
219
|
+
for (const candidate of candidates) {
|
|
220
|
+
const record = asRecordOrNull(candidate);
|
|
221
|
+
if (!record) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const filePath = typeof record.filePath === "string"
|
|
225
|
+
? record.filePath
|
|
226
|
+
: typeof record.file_path === "string"
|
|
227
|
+
? record.file_path
|
|
228
|
+
: "";
|
|
229
|
+
const content = typeof record.content === "string" ? record.content : "";
|
|
230
|
+
const originalRaw = "originalFile" in record ? record.originalFile : "original_file" in record ? record.original_file : undefined;
|
|
231
|
+
if (!filePath || !content || originalRaw === undefined) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const original = typeof originalRaw === "string" ? originalRaw : originalRaw === null ? "" : "";
|
|
235
|
+
return [{ type: "diff", old_path: filePath, new_path: filePath, old: original, new: content }];
|
|
236
|
+
}
|
|
237
|
+
return [];
|
|
238
|
+
}
|
|
239
|
+
export function buildToolResultFields(isError, rawContent, base) {
|
|
240
|
+
const rawOutput = normalizeToolResultText(rawContent);
|
|
241
|
+
const toolName = resolveToolName(base);
|
|
242
|
+
const fields = {
|
|
243
|
+
status: isError ? "failed" : "completed",
|
|
244
|
+
raw_output: rawOutput || JSON.stringify(rawContent),
|
|
245
|
+
};
|
|
246
|
+
if (!isError && toolName === "Write") {
|
|
247
|
+
const structuredDiff = writeDiffFromResult(rawContent);
|
|
248
|
+
if (structuredDiff.length > 0) {
|
|
249
|
+
fields.content = structuredDiff;
|
|
250
|
+
return fields;
|
|
251
|
+
}
|
|
252
|
+
const inputDiff = writeDiffFromInput(base?.raw_input);
|
|
253
|
+
if (inputDiff.length > 0) {
|
|
254
|
+
fields.content = inputDiff;
|
|
255
|
+
return fields;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (!isError && toolName === "Edit") {
|
|
259
|
+
const inputDiff = editDiffFromInput(base?.raw_input);
|
|
260
|
+
if (inputDiff.length > 0) {
|
|
261
|
+
fields.content = inputDiff;
|
|
262
|
+
return fields;
|
|
263
|
+
}
|
|
264
|
+
if (base?.content.some((entry) => entry.type === "diff")) {
|
|
265
|
+
return fields;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (rawOutput) {
|
|
269
|
+
fields.content = [{ type: "content", content: { type: "text", text: rawOutput } }];
|
|
270
|
+
}
|
|
271
|
+
return fields;
|
|
272
|
+
}
|
|
273
|
+
export function unwrapToolUseResult(rawResult) {
|
|
274
|
+
if (!rawResult || typeof rawResult !== "object") {
|
|
275
|
+
return { isError: false, content: rawResult };
|
|
276
|
+
}
|
|
277
|
+
const record = rawResult;
|
|
278
|
+
const isError = (typeof record.is_error === "boolean" && record.is_error) ||
|
|
279
|
+
(typeof record.error === "boolean" && record.error);
|
|
280
|
+
if ("content" in record) {
|
|
281
|
+
return { isError: Boolean(isError), content: record.content };
|
|
282
|
+
}
|
|
283
|
+
if ("result" in record) {
|
|
284
|
+
return { isError: Boolean(isError), content: record.result };
|
|
285
|
+
}
|
|
286
|
+
if ("text" in record) {
|
|
287
|
+
return { isError: Boolean(isError), content: record.text };
|
|
288
|
+
}
|
|
289
|
+
return { isError: Boolean(isError), content: rawResult };
|
|
290
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { asRecordOrNull } from "./shared.js";
|
|
2
|
+
function numberField(record, ...keys) {
|
|
3
|
+
for (const key of keys) {
|
|
4
|
+
const value = record[key];
|
|
5
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
function selectModelUsageRecord(session, message) {
|
|
12
|
+
const modelUsageRaw = asRecordOrNull(message.modelUsage);
|
|
13
|
+
if (!modelUsageRaw) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const sortedKeys = Object.keys(modelUsageRaw).sort();
|
|
17
|
+
if (sortedKeys.length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const preferredKeys = new Set();
|
|
21
|
+
if (session?.model) {
|
|
22
|
+
preferredKeys.add(session.model);
|
|
23
|
+
}
|
|
24
|
+
if (typeof message.model === "string") {
|
|
25
|
+
preferredKeys.add(message.model);
|
|
26
|
+
}
|
|
27
|
+
for (const key of preferredKeys) {
|
|
28
|
+
const value = asRecordOrNull(modelUsageRaw[key]);
|
|
29
|
+
if (value) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const key of sortedKeys) {
|
|
34
|
+
const value = asRecordOrNull(modelUsageRaw[key]);
|
|
35
|
+
if (value) {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
export function buildUsageUpdateFromResultForSession(session, message) {
|
|
42
|
+
const usage = asRecordOrNull(message.usage);
|
|
43
|
+
const inputTokens = usage ? numberField(usage, "inputTokens", "input_tokens") : undefined;
|
|
44
|
+
const outputTokens = usage ? numberField(usage, "outputTokens", "output_tokens") : undefined;
|
|
45
|
+
const cacheReadTokens = usage
|
|
46
|
+
? numberField(usage, "cacheReadInputTokens", "cache_read_input_tokens", "cache_read_tokens")
|
|
47
|
+
: undefined;
|
|
48
|
+
const cacheWriteTokens = usage
|
|
49
|
+
? numberField(usage, "cacheCreationInputTokens", "cache_creation_input_tokens", "cache_write_tokens")
|
|
50
|
+
: undefined;
|
|
51
|
+
const totalCostUsd = numberField(message, "total_cost_usd", "totalCostUsd");
|
|
52
|
+
let turnCostUsd;
|
|
53
|
+
if (totalCostUsd !== undefined && session) {
|
|
54
|
+
if (session.lastTotalCostUsd === undefined) {
|
|
55
|
+
turnCostUsd = totalCostUsd;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
turnCostUsd = Math.max(0, totalCostUsd - session.lastTotalCostUsd);
|
|
59
|
+
}
|
|
60
|
+
session.lastTotalCostUsd = totalCostUsd;
|
|
61
|
+
}
|
|
62
|
+
const modelUsage = selectModelUsageRecord(session, message);
|
|
63
|
+
const contextWindow = modelUsage
|
|
64
|
+
? numberField(modelUsage, "contextWindow", "context_window")
|
|
65
|
+
: undefined;
|
|
66
|
+
const maxOutputTokens = modelUsage
|
|
67
|
+
? numberField(modelUsage, "maxOutputTokens", "max_output_tokens")
|
|
68
|
+
: undefined;
|
|
69
|
+
if (inputTokens === undefined &&
|
|
70
|
+
outputTokens === undefined &&
|
|
71
|
+
cacheReadTokens === undefined &&
|
|
72
|
+
cacheWriteTokens === undefined &&
|
|
73
|
+
totalCostUsd === undefined &&
|
|
74
|
+
turnCostUsd === undefined &&
|
|
75
|
+
contextWindow === undefined &&
|
|
76
|
+
maxOutputTokens === undefined) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
type: "usage_update",
|
|
81
|
+
usage: {
|
|
82
|
+
...(inputTokens !== undefined ? { input_tokens: inputTokens } : {}),
|
|
83
|
+
...(outputTokens !== undefined ? { output_tokens: outputTokens } : {}),
|
|
84
|
+
...(cacheReadTokens !== undefined ? { cache_read_tokens: cacheReadTokens } : {}),
|
|
85
|
+
...(cacheWriteTokens !== undefined ? { cache_write_tokens: cacheWriteTokens } : {}),
|
|
86
|
+
...(totalCostUsd !== undefined ? { total_cost_usd: totalCostUsd } : {}),
|
|
87
|
+
...(turnCostUsd !== undefined ? { turn_cost_usd: turnCostUsd } : {}),
|
|
88
|
+
...(contextWindow !== undefined ? { context_window: contextWindow } : {}),
|
|
89
|
+
...(maxOutputTokens !== undefined ? { max_output_tokens: maxOutputTokens } : {}),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function buildUsageUpdateFromResult(message) {
|
|
94
|
+
return buildUsageUpdateFromResultForSession(undefined, message);
|
|
95
|
+
}
|