cursor-opencode-provider 0.6.4 → 0.6.7
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 +17 -6
- package/dist/auth.js +28 -11
- package/dist/language-model.d.ts +2 -0
- package/dist/language-model.js +273 -65
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +83 -19
- package/dist/opencode2/catalog.js +4 -3
- package/dist/pricing-data.d.ts +81 -0
- package/dist/pricing-data.js +85 -4
- package/dist/pricing.d.ts +10 -4
- package/dist/pricing.js +17 -8
- package/dist/protocol/exec-variants.d.ts +5 -0
- package/dist/protocol/exec-variants.js +17 -2
- package/dist/protocol/git-diff.d.ts +33 -0
- package/dist/protocol/git-diff.js +372 -0
- package/dist/protocol/messages.js +229 -6
- package/dist/protocol/progress-continuation.d.ts +25 -0
- package/dist/protocol/progress-continuation.js +92 -0
- package/dist/protocol/tools.d.ts +6 -0
- package/dist/protocol/tools.js +216 -3
- package/dist/protocol/workspace-grounding.d.ts +1 -0
- package/dist/protocol/workspace-grounding.js +8 -0
- package/dist/session.d.ts +11 -2
- package/dist/session.js +34 -5
- package/dist/usage.d.ts +14 -0
- package/dist/usage.js +26 -0
- package/package.json +1 -1
package/dist/protocol/tools.js
CHANGED
|
@@ -6,7 +6,7 @@ import { encodeJsonAsValue, decodeStructEntriesToJson, readAllFields } from "./s
|
|
|
6
6
|
import { buildEnv } from "../context/env.js";
|
|
7
7
|
import { ensureOpencodeProjectDir } from "../context/paths.js";
|
|
8
8
|
import { trace, traceRequestContextPaths } from "../debug.js";
|
|
9
|
-
import { cursorExecVariantByRequestName } from "./exec-variants.js";
|
|
9
|
+
import { cursorExecVariantByRequestName, FORCE_BACKGROUND_STATUS_ERROR, } from "./exec-variants.js";
|
|
10
10
|
import { APPLY_PATCH_TOOL, buildAddFilePatch, buildUpdateFilePatch, planSubstringEdit, } from "./apply-patch.js";
|
|
11
11
|
import { BACKGROUND_SHELL_MARKER, buildBackgroundShellCommand, } from "../shell-timeout.js";
|
|
12
12
|
// Exec variant field number whose reply is the server-initiated request_context
|
|
@@ -233,6 +233,8 @@ export function buildLiveRequestContext(tools, providerIdentifier = "opencode",
|
|
|
233
233
|
// does not. This provider intentionally remaps:
|
|
234
234
|
// - delete_args → bash `rm -f -- <quoted-path>`
|
|
235
235
|
// - background_shell_spawn_args → bash (original command; plugin wraps nohup)
|
|
236
|
+
// - shell_args (#2) → bash, same timeout/background path as shell_stream; the
|
|
237
|
+
// reply stays ShellResult (not the streaming oneof).
|
|
236
238
|
// Permissions still flow through OpenCode's advertised `bash` tool. Soft-
|
|
237
239
|
// background / detached children can outlive the OpenCode tool call; leftover
|
|
238
240
|
// process cleanup is left to the user / OS. Arg key remapping happens below.
|
|
@@ -249,6 +251,7 @@ const cursorToolToOpencode = {
|
|
|
249
251
|
grep_args: "grep",
|
|
250
252
|
ls_args: "read",
|
|
251
253
|
delete_args: "bash",
|
|
254
|
+
shell_args: "bash",
|
|
252
255
|
shell_stream_args: "bash",
|
|
253
256
|
background_shell_spawn_args: "bash",
|
|
254
257
|
subagent_args: "task",
|
|
@@ -793,7 +796,7 @@ export function parseExecServerMessage(msg) {
|
|
|
793
796
|
"pi_read_args", "pi_bash_args", "pi_edit_args", "pi_write_args",
|
|
794
797
|
"pi_grep_args", "pi_find_args", "pi_ls_args",
|
|
795
798
|
"grep_args", "ls_args",
|
|
796
|
-
"delete_args", "shell_stream_args", "background_shell_spawn_args", "mcp_args",
|
|
799
|
+
"delete_args", "shell_args", "shell_stream_args", "background_shell_spawn_args", "mcp_args",
|
|
797
800
|
"subagent_args",
|
|
798
801
|
]);
|
|
799
802
|
if (!execVariant)
|
|
@@ -915,7 +918,7 @@ export function parseExecServerMessage(msg) {
|
|
|
915
918
|
return undefined;
|
|
916
919
|
const mapped = mapCursorArgsToOpencode(toolName, msg[execVariant] ?? {}, execVariant);
|
|
917
920
|
const rawArgs = msg[execVariant] ?? {};
|
|
918
|
-
const resultMetadata = execVariant === "shell_stream_args"
|
|
921
|
+
const resultMetadata = execVariant === "shell_stream_args" || execVariant === "shell_args"
|
|
919
922
|
? shellStreamResultMetadata(rawArgs)
|
|
920
923
|
: execVariant === "read_args"
|
|
921
924
|
? {
|
|
@@ -1412,6 +1415,164 @@ export function buildExecStreamClose(execId) {
|
|
|
1412
1415
|
},
|
|
1413
1416
|
});
|
|
1414
1417
|
}
|
|
1418
|
+
export function buildUnsupportedExecDeny(input) {
|
|
1419
|
+
const { execId, variant, reason } = input;
|
|
1420
|
+
const resultName = variant.resultName;
|
|
1421
|
+
const frames = [];
|
|
1422
|
+
const throwFrame = (msg) => encodeMessage("AgentClientMessage", {
|
|
1423
|
+
exec_client_control_message: {
|
|
1424
|
+
throw: { id: execId, error: msg },
|
|
1425
|
+
},
|
|
1426
|
+
});
|
|
1427
|
+
switch (resultName) {
|
|
1428
|
+
case "shell_result":
|
|
1429
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1430
|
+
exec_client_message: {
|
|
1431
|
+
id: execId,
|
|
1432
|
+
local_execution_time_ms: 0,
|
|
1433
|
+
shell_result: { rejected: { reason } },
|
|
1434
|
+
},
|
|
1435
|
+
}));
|
|
1436
|
+
break;
|
|
1437
|
+
case "diagnostics_result":
|
|
1438
|
+
case "canvas_diagnostics_result":
|
|
1439
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1440
|
+
exec_client_message: {
|
|
1441
|
+
id: execId,
|
|
1442
|
+
local_execution_time_ms: 0,
|
|
1443
|
+
[resultName]: { error: { path: "", error: reason } },
|
|
1444
|
+
},
|
|
1445
|
+
}));
|
|
1446
|
+
break;
|
|
1447
|
+
case "fetch_result":
|
|
1448
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1449
|
+
exec_client_message: {
|
|
1450
|
+
id: execId,
|
|
1451
|
+
local_execution_time_ms: 0,
|
|
1452
|
+
fetch_result: { error: { url: "", error: reason } },
|
|
1453
|
+
},
|
|
1454
|
+
}));
|
|
1455
|
+
break;
|
|
1456
|
+
case "record_screen_result":
|
|
1457
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1458
|
+
exec_client_message: {
|
|
1459
|
+
id: execId,
|
|
1460
|
+
local_execution_time_ms: 0,
|
|
1461
|
+
record_screen_result: { failure: { error: reason } },
|
|
1462
|
+
},
|
|
1463
|
+
}));
|
|
1464
|
+
break;
|
|
1465
|
+
case "computer_use_result":
|
|
1466
|
+
case "write_shell_stdin_result":
|
|
1467
|
+
case "smart_mode_classifier_result":
|
|
1468
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1469
|
+
exec_client_message: {
|
|
1470
|
+
id: execId,
|
|
1471
|
+
local_execution_time_ms: 0,
|
|
1472
|
+
[resultName]: { error: { error: reason } },
|
|
1473
|
+
},
|
|
1474
|
+
}));
|
|
1475
|
+
break;
|
|
1476
|
+
case "redacted_read_result":
|
|
1477
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1478
|
+
exec_client_message: {
|
|
1479
|
+
id: execId,
|
|
1480
|
+
local_execution_time_ms: 0,
|
|
1481
|
+
redacted_read_result: { error: { path: "", error: reason } },
|
|
1482
|
+
},
|
|
1483
|
+
}));
|
|
1484
|
+
break;
|
|
1485
|
+
case "subagent_await_result":
|
|
1486
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1487
|
+
exec_client_message: {
|
|
1488
|
+
id: execId,
|
|
1489
|
+
local_execution_time_ms: 0,
|
|
1490
|
+
subagent_await_result: { error: { error: reason } },
|
|
1491
|
+
},
|
|
1492
|
+
}));
|
|
1493
|
+
break;
|
|
1494
|
+
case "shell_allowlist_precheck_result":
|
|
1495
|
+
case "mcp_allowlist_precheck_result":
|
|
1496
|
+
case "web_fetch_allowlist_precheck_result":
|
|
1497
|
+
// Typed precheck result is a bool, not a reason string. Encoding
|
|
1498
|
+
// `{allowlisted:false}` selects the ExecClientMessage oneof; protobufjs
|
|
1499
|
+
// writes field 1 as varint 0 so Cursor decodes the deny rather than an
|
|
1500
|
+
// empty/unspecified precheck.
|
|
1501
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1502
|
+
exec_client_message: {
|
|
1503
|
+
id: execId,
|
|
1504
|
+
local_execution_time_ms: 0,
|
|
1505
|
+
[resultName]: { allowlisted: false },
|
|
1506
|
+
},
|
|
1507
|
+
}));
|
|
1508
|
+
break;
|
|
1509
|
+
case "force_background_shell_result":
|
|
1510
|
+
case "force_background_subagent_result":
|
|
1511
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1512
|
+
exec_client_message: {
|
|
1513
|
+
id: execId,
|
|
1514
|
+
local_execution_time_ms: 0,
|
|
1515
|
+
[resultName]: { status: FORCE_BACKGROUND_STATUS_ERROR },
|
|
1516
|
+
},
|
|
1517
|
+
}));
|
|
1518
|
+
break;
|
|
1519
|
+
case "mini_swe_agent_bash_result":
|
|
1520
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1521
|
+
exec_client_message: {
|
|
1522
|
+
id: execId,
|
|
1523
|
+
local_execution_time_ms: 0,
|
|
1524
|
+
mini_swe_agent_bash_result: { rejected: { reason } },
|
|
1525
|
+
},
|
|
1526
|
+
}));
|
|
1527
|
+
break;
|
|
1528
|
+
case "conversation_search_result":
|
|
1529
|
+
// No local Cursor conversation index. Empty success is truthful and
|
|
1530
|
+
// avoids a retry loop; the model can continue with listed tools.
|
|
1531
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1532
|
+
exec_client_message: {
|
|
1533
|
+
id: execId,
|
|
1534
|
+
local_execution_time_ms: 0,
|
|
1535
|
+
conversation_search_result: {
|
|
1536
|
+
success: { hits: [], truncated: false, partial: false, rebuilding: false },
|
|
1537
|
+
},
|
|
1538
|
+
},
|
|
1539
|
+
}));
|
|
1540
|
+
break;
|
|
1541
|
+
case "agent_store_conflict_result":
|
|
1542
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1543
|
+
exec_client_message: {
|
|
1544
|
+
id: execId,
|
|
1545
|
+
local_execution_time_ms: 0,
|
|
1546
|
+
agent_store_conflict_result: { error: { error: reason } },
|
|
1547
|
+
},
|
|
1548
|
+
}));
|
|
1549
|
+
break;
|
|
1550
|
+
case "adopt_result":
|
|
1551
|
+
frames.push(encodeMessage("AgentClientMessage", {
|
|
1552
|
+
exec_client_message: {
|
|
1553
|
+
id: execId,
|
|
1554
|
+
local_execution_time_ms: 0,
|
|
1555
|
+
adopt_result: { error: reason },
|
|
1556
|
+
},
|
|
1557
|
+
}));
|
|
1558
|
+
break;
|
|
1559
|
+
case "execute_hook_result":
|
|
1560
|
+
// agent.proto has no populated error oneof we can emit;
|
|
1561
|
+
// Cursor CLI answers this through exec_client_control_message.throw.
|
|
1562
|
+
frames.push(throwFrame(reason));
|
|
1563
|
+
frames.push(buildExecStreamClose(execId));
|
|
1564
|
+
return frames;
|
|
1565
|
+
default:
|
|
1566
|
+
// Fallback for any future unsupported variant without a dedicated shape:
|
|
1567
|
+
// typed error when possible, else throw. Unknown shapes should hard-fail
|
|
1568
|
+
// at the pump, so this path is not expected for known unsupported rows.
|
|
1569
|
+
frames.push(throwFrame(reason));
|
|
1570
|
+
frames.push(buildExecStreamClose(execId));
|
|
1571
|
+
return frames;
|
|
1572
|
+
}
|
|
1573
|
+
frames.push(buildExecStreamClose(execId));
|
|
1574
|
+
return frames;
|
|
1575
|
+
}
|
|
1415
1576
|
/**
|
|
1416
1577
|
* Strip opencode's `read` envelope, leaving raw file content.
|
|
1417
1578
|
*
|
|
@@ -1570,6 +1731,58 @@ export function buildTypedExecResult(resultField, output, error, toolName, resul
|
|
|
1570
1731
|
const truncation = readTruncationMessage(output, content);
|
|
1571
1732
|
return { success: { output: content, ...(truncation ? { truncation } : {}) } };
|
|
1572
1733
|
}
|
|
1734
|
+
case "shell_result": {
|
|
1735
|
+
const command = str(resultMetadata?.command) ?? "";
|
|
1736
|
+
const workingDirectory = str(resultMetadata?.working_directory) ?? "";
|
|
1737
|
+
if (error) {
|
|
1738
|
+
return {
|
|
1739
|
+
failure: {
|
|
1740
|
+
command,
|
|
1741
|
+
working_directory: workingDirectory,
|
|
1742
|
+
exit_code: 1,
|
|
1743
|
+
stdout: output || "",
|
|
1744
|
+
stderr: error,
|
|
1745
|
+
aborted: false,
|
|
1746
|
+
},
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
if (shellOutcome?.kind === "timeout") {
|
|
1750
|
+
return {
|
|
1751
|
+
timeout: {
|
|
1752
|
+
command,
|
|
1753
|
+
working_directory: workingDirectory,
|
|
1754
|
+
timeout_ms: shellOutcome.timeoutMs,
|
|
1755
|
+
},
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
if (shellOutcome?.kind === "backgrounded") {
|
|
1759
|
+
return {
|
|
1760
|
+
success: {
|
|
1761
|
+
command: shellOutcome.command || command,
|
|
1762
|
+
working_directory: shellOutcome.workingDirectory || workingDirectory,
|
|
1763
|
+
exit_code: 0,
|
|
1764
|
+
stdout: output,
|
|
1765
|
+
shell_id: shellOutcome.shellId,
|
|
1766
|
+
pid: shellOutcome.pid,
|
|
1767
|
+
ms_to_wait: shellOutcome.msToWait,
|
|
1768
|
+
background_reason: shellOutcome.reason,
|
|
1769
|
+
},
|
|
1770
|
+
is_background: true,
|
|
1771
|
+
pid: shellOutcome.pid,
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
const exitCode = shellOutcome?.kind === "exit"
|
|
1775
|
+
? Math.max(0, Math.min(0xffff_ffff, shellOutcome.code))
|
|
1776
|
+
: 0;
|
|
1777
|
+
return {
|
|
1778
|
+
success: {
|
|
1779
|
+
command,
|
|
1780
|
+
working_directory: workingDirectory,
|
|
1781
|
+
exit_code: exitCode,
|
|
1782
|
+
stdout: output,
|
|
1783
|
+
},
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1573
1786
|
case "pi_bash_result":
|
|
1574
1787
|
case "pi_edit_result":
|
|
1575
1788
|
case "pi_grep_result":
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function appendWorkspaceRootGrounding(reason: string, workspaceRoot: string | undefined): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function appendWorkspaceRootGrounding(reason, workspaceRoot) {
|
|
2
|
+
if (!workspaceRoot || !workspaceRoot.trim())
|
|
3
|
+
return reason;
|
|
4
|
+
if (reason.includes("Workspace root:"))
|
|
5
|
+
return reason;
|
|
6
|
+
return (`${reason}\nWorkspace root: ${JSON.stringify(workspaceRoot)}. ` +
|
|
7
|
+
"Resolve workspace paths against exactly this root; never invent an absolute prefix, and verify uncertain paths with an available tool before using them.");
|
|
8
|
+
}
|
package/dist/session.d.ts
CHANGED
|
@@ -175,8 +175,9 @@ export type CursorSession = {
|
|
|
175
175
|
*/
|
|
176
176
|
allowTools: boolean;
|
|
177
177
|
/**
|
|
178
|
-
* Best-effort held-Run activity counters used only for diagnostics.
|
|
179
|
-
*
|
|
178
|
+
* Best-effort held-Run activity counters used only for diagnostics. Displayed
|
|
179
|
+
* AI SDK usage comes from checkpoint occupancy (tool steps) and TurnEnded
|
|
180
|
+
* (stop); this estimate is never sent to OpenCode.
|
|
180
181
|
*/
|
|
181
182
|
usageEstimate: {
|
|
182
183
|
inputTokens: number;
|
|
@@ -207,6 +208,7 @@ export type CursorSession = {
|
|
|
207
208
|
semanticDeadlineAt: number;
|
|
208
209
|
closeError: CursorProviderError | null;
|
|
209
210
|
closed: boolean;
|
|
211
|
+
reopenWithUserMessage?: (text: string) => Promise<void>;
|
|
210
212
|
};
|
|
211
213
|
type SessionManagerOptions = {
|
|
212
214
|
now?: () => number;
|
|
@@ -255,6 +257,13 @@ export declare class SessionManager {
|
|
|
255
257
|
beginPump(session: CursorSession, owner: symbol): void;
|
|
256
258
|
isPumpOwner(session: CursorSession, owner: symbol): boolean;
|
|
257
259
|
endPump(session: CursorSession, owner: symbol): boolean;
|
|
260
|
+
/**
|
|
261
|
+
* Swap the live bidi Run while a pump is still pulling frames.
|
|
262
|
+
* Callers must cancel the session heartbeat and wait for the old stream's
|
|
263
|
+
* write chain before this, so an in-flight heartbeat cannot close the session.
|
|
264
|
+
*/
|
|
265
|
+
replaceStream(session: CursorSession, next: BidiStream): void;
|
|
266
|
+
private subscribeTerminal;
|
|
258
267
|
private key;
|
|
259
268
|
close(session: CursorSession, reason?: SessionCloseReason, error?: CursorProviderError): void;
|
|
260
269
|
/**
|
package/dist/session.js
CHANGED
|
@@ -118,11 +118,7 @@ export class SessionManager {
|
|
|
118
118
|
this.byOpenCodeSessionId.set(session.openCodeSessionId, session);
|
|
119
119
|
}
|
|
120
120
|
this.enforceOpenSessionCap(session);
|
|
121
|
-
|
|
122
|
-
if (session.closed)
|
|
123
|
-
unsubscribe();
|
|
124
|
-
else
|
|
125
|
-
session.terminalUnsubscribe = unsubscribe;
|
|
121
|
+
this.subscribeTerminal(session);
|
|
126
122
|
}
|
|
127
123
|
isPumping(session) {
|
|
128
124
|
return session.pumpOwner != null || session.pumpActive;
|
|
@@ -325,6 +321,39 @@ export class SessionManager {
|
|
|
325
321
|
this.close(session, deferred);
|
|
326
322
|
return true;
|
|
327
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Swap the live bidi Run while a pump is still pulling frames.
|
|
326
|
+
* Callers must cancel the session heartbeat and wait for the old stream's
|
|
327
|
+
* write chain before this, so an in-flight heartbeat cannot close the session.
|
|
328
|
+
*/
|
|
329
|
+
replaceStream(session, next) {
|
|
330
|
+
if (session.closed)
|
|
331
|
+
throw new CursorProtocolError("Cannot replace stream on a closed Cursor session");
|
|
332
|
+
session.heartbeatCancel?.();
|
|
333
|
+
session.terminalUnsubscribe?.();
|
|
334
|
+
session.terminalUnsubscribe = null;
|
|
335
|
+
session.deferredTerminalReason = null;
|
|
336
|
+
const old = session.stream;
|
|
337
|
+
session.stream = next;
|
|
338
|
+
session.frames = next.frames()[Symbol.asyncIterator]();
|
|
339
|
+
this.subscribeTerminal(session);
|
|
340
|
+
try {
|
|
341
|
+
old.destroy();
|
|
342
|
+
}
|
|
343
|
+
catch { /* already closed */ }
|
|
344
|
+
}
|
|
345
|
+
subscribeTerminal(session) {
|
|
346
|
+
const boundStream = session.stream;
|
|
347
|
+
const unsubscribe = boundStream.onTerminal?.((event) => {
|
|
348
|
+
if (session.stream !== boundStream)
|
|
349
|
+
return;
|
|
350
|
+
this.onStreamTerminal(session, event);
|
|
351
|
+
}) ?? (() => { });
|
|
352
|
+
if (session.closed)
|
|
353
|
+
unsubscribe();
|
|
354
|
+
else
|
|
355
|
+
session.terminalUnsubscribe = unsubscribe;
|
|
356
|
+
}
|
|
328
357
|
key(sessionId, execId) {
|
|
329
358
|
return `${sessionId}:${execId}`;
|
|
330
359
|
}
|
package/dist/usage.d.ts
CHANGED
|
@@ -51,6 +51,20 @@ export declare function formatCursorCacheDiagnostics(counters: CursorUsageCounte
|
|
|
51
51
|
export declare function formatTurnUsageValidation(counters: CursorUsageCounters, usage: LanguageModelV3Usage, tokenDetails?: CursorConversationTokenDetails, contextSource?: CursorContextUsageSource): string;
|
|
52
52
|
/** OpenCode requires a usage object at every step boundary. */
|
|
53
53
|
export declare function emptyLanguageModelV3Usage(): LanguageModelV3Usage;
|
|
54
|
+
/**
|
|
55
|
+
* OpenCode TUI/GUI replace each assistant message's `tokens` (they do not sum
|
|
56
|
+
* occupancy). The TUI footer picks the last assistant with `tokens.output > 0`.
|
|
57
|
+
* Session cost, however, adds every step-finish. Checkpoint occupancy is
|
|
58
|
+
* therefore sent as a snapshot with `output=1` so the footer accepts it, and
|
|
59
|
+
* callers attach {@link OPENCODE_DISPLAY_ONLY_COST_METADATA} so getUsage
|
|
60
|
+
* reports $0 instead of billing the snapshot as a new prompt.
|
|
61
|
+
*/
|
|
62
|
+
export declare const OPENCODE_DISPLAY_ONLY_COST_METADATA: {
|
|
63
|
+
readonly copilot: {
|
|
64
|
+
readonly totalNanoAiu: 0;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
export declare function occupancyUsageFromTokenDetails(details: CursorConversationTokenDetails, prior?: CursorConversationTokenDetails): LanguageModelV3Usage;
|
|
54
68
|
/** Project nested V3 usage into the common flat AI-SDK counter shape. */
|
|
55
69
|
export declare function flatUsageFromV3(usage: LanguageModelV3Usage): {
|
|
56
70
|
inputTokens: number;
|
package/dist/usage.js
CHANGED
|
@@ -229,6 +229,32 @@ export function emptyLanguageModelV3Usage() {
|
|
|
229
229
|
reasoningTokens: 0,
|
|
230
230
|
});
|
|
231
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* OpenCode TUI/GUI replace each assistant message's `tokens` (they do not sum
|
|
234
|
+
* occupancy). The TUI footer picks the last assistant with `tokens.output > 0`.
|
|
235
|
+
* Session cost, however, adds every step-finish. Checkpoint occupancy is
|
|
236
|
+
* therefore sent as a snapshot with `output=1` so the footer accepts it, and
|
|
237
|
+
* callers attach {@link OPENCODE_DISPLAY_ONLY_COST_METADATA} so getUsage
|
|
238
|
+
* reports $0 instead of billing the snapshot as a new prompt.
|
|
239
|
+
*/
|
|
240
|
+
export const OPENCODE_DISPLAY_ONLY_COST_METADATA = {
|
|
241
|
+
copilot: { totalNanoAiu: 0 },
|
|
242
|
+
};
|
|
243
|
+
export function occupancyUsageFromTokenDetails(details, prior) {
|
|
244
|
+
const used = Math.max(0, Math.trunc(details.usedTokens));
|
|
245
|
+
if (used <= 0)
|
|
246
|
+
return emptyLanguageModelV3Usage();
|
|
247
|
+
return buildLanguageModelV3UsageFromCounters({
|
|
248
|
+
inputTokens: used,
|
|
249
|
+
outputTokens: 1,
|
|
250
|
+
cacheRead: prior?.usedTokens ?? 0,
|
|
251
|
+
cacheWrite: 0,
|
|
252
|
+
reasoningTokens: 0,
|
|
253
|
+
}, {
|
|
254
|
+
contextTotalTokens: used,
|
|
255
|
+
priorContextTokens: prior?.usedTokens,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
232
258
|
/** Project nested V3 usage into the common flat AI-SDK counter shape. */
|
|
233
259
|
export function flatUsageFromV3(usage) {
|
|
234
260
|
const input = usage.inputTokens;
|