mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 23:04:05 +00:00
Adds the opt-in bundled GitHub Copilot agent runtime, pinned SDK install path, docs/inventory, SDK/tool/sandbox/auth wiring, and replay/tool-safety fixes.
Verification:
- Local: git diff --check; fnm exec --using 24.15.0 pnpm tsgo:extensions; fnm exec --using 24.15.0 pnpm check:test-types; fnm exec --using 24.15.0 pnpm build.
- Autoreview local: clean for the replay-safety fix; branch autoreview engine returned empty output twice, so local autoreview plus local/Crabbox/CI proof was used.
- Crabbox focused Copilot: run_2c0db9f48a4a, 19 files / 485 tests passed.
- Crabbox additional boundary shard: run_26a246a1aa24, prompt snapshots and plugin SDK boundary/export checks passed.
- Crabbox live Copilot: run_d128e4048b4e, real gpt-4.1 turn with live_echo phase-1-green and clean session-file check.
- GitHub checks: green on head 7cc8657e0d, including Dependency Guard after exact-head approval.
Co-authored-by: Ramraj Balasubramanian <ramrajba@microsoft.com>
84 lines
2.3 KiB
TypeScript
84 lines
2.3 KiB
TypeScript
import type { AgentMessage, NormalizedUsage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
|
|
type AssistantMessage = Extract<AgentMessage, { role: "assistant" }>;
|
|
type AssistantUsage = NonNullable<AssistantMessage["usage"]>;
|
|
|
|
type CopilotUsageSource = {
|
|
cacheReadTokens?: unknown;
|
|
cacheWriteTokens?: unknown;
|
|
inputTokens?: unknown;
|
|
outputTokens?: unknown;
|
|
};
|
|
|
|
export type CopilotUsageSnapshot = NormalizedUsage;
|
|
|
|
function isCopilotUsageSource(data: unknown): data is CopilotUsageSource {
|
|
return typeof data === "object" && data !== null;
|
|
}
|
|
|
|
function buildZeroCost(): AssistantUsage["cost"] {
|
|
return {
|
|
cacheRead: 0,
|
|
cacheWrite: 0,
|
|
input: 0,
|
|
output: 0,
|
|
total: 0,
|
|
};
|
|
}
|
|
|
|
function coerceTokenCount(value: unknown): number | undefined {
|
|
return typeof value === "number" && Number.isFinite(value)
|
|
? Math.max(0, Math.trunc(value))
|
|
: undefined;
|
|
}
|
|
|
|
export function normalizeCopilotUsage(data: unknown): NormalizedUsage | undefined {
|
|
if (!isCopilotUsageSource(data)) {
|
|
return undefined;
|
|
}
|
|
|
|
// SDK usage events only expose these four fields. Keep coercion identical to
|
|
// the prior event-bridge implementation so invalid object-shaped events still
|
|
// overwrite state with the legacy all-zero snapshot.
|
|
const input = coerceTokenCount(data.inputTokens);
|
|
const output = coerceTokenCount(data.outputTokens);
|
|
const cacheRead = coerceTokenCount(data.cacheReadTokens);
|
|
const cacheWrite = coerceTokenCount(data.cacheWriteTokens);
|
|
const total = (input ?? 0) + (output ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0);
|
|
|
|
return {
|
|
cacheRead,
|
|
cacheWrite,
|
|
input,
|
|
output,
|
|
total,
|
|
};
|
|
}
|
|
|
|
export function buildCopilotAssistantUsage(params: {
|
|
usage?: NormalizedUsage;
|
|
fallbackOutputTokens?: unknown;
|
|
}): AssistantMessage["usage"] {
|
|
const usage =
|
|
params.usage ?? normalizeCopilotUsage({ outputTokens: params.fallbackOutputTokens });
|
|
|
|
return {
|
|
cacheRead: usage?.cacheRead ?? 0,
|
|
cacheWrite: usage?.cacheWrite ?? 0,
|
|
cost: buildZeroCost(),
|
|
input: usage?.input ?? 0,
|
|
output: usage?.output ?? 0,
|
|
totalTokens: usage?.total ?? 0,
|
|
};
|
|
}
|
|
|
|
export function deriveCopilotUsageTotal(usage?: NormalizedUsage): number | undefined {
|
|
if (!usage) {
|
|
return undefined;
|
|
}
|
|
|
|
return (
|
|
(usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0)
|
|
);
|
|
}
|