feat: TypeScript SDK (agentlens-sdk) and OpenCode plugin (opencode-agentlens)

- packages/sdk-ts: BatchTransport, TraceBuilder, models, decision helpers
  Zero external deps, native fetch, ESM+CJS output
- packages/opencode-plugin: OpenCode plugin with hooks for:
  - Session lifecycle (create/idle/error/delete/diff)
  - Tool execution capture (before/after -> TOOL_CALL spans + TOOL_SELECTION decisions)
  - LLM call tracking (chat.message -> LLM_CALL spans with model/provider)
  - Permission flow (permission.ask -> ESCALATION decisions)
  - File edit events
  - Model cost estimation (Claude, GPT-4o, o3-mini pricing)
This commit is contained in:
Vectry
2026-02-10 03:08:51 +00:00
parent 0149e0a6f4
commit 6bed493275
17 changed files with 2589 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
import type { JsonValue } from "agentlens-sdk";
export function truncate(str: string, maxLength: number): string {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength) + "... [truncated]";
}
export function extractToolMetadata(
tool: string,
args: unknown,
): Record<string, unknown> {
const a = args as Record<string, unknown> | null | undefined;
if (!a || typeof a !== "object") return {};
switch (tool) {
case "read":
case "mcp_read":
return { filePath: a["filePath"] };
case "write":
case "mcp_write":
return { filePath: a["filePath"] };
case "edit":
case "mcp_edit":
return { filePath: a["filePath"] };
case "bash":
case "mcp_bash":
return {
command: truncate(String(a["command"] ?? ""), 200),
};
case "glob":
case "mcp_glob":
return { pattern: a["pattern"] };
case "grep":
case "mcp_grep":
return { pattern: a["pattern"], path: a["path"] };
case "task":
case "mcp_task":
return {
category: a["category"],
description: a["description"],
};
default:
return {};
}
}
const MODEL_COSTS: Record<string, { input: number; output: number }> = {
"claude-opus-4-20250514": { input: 15, output: 75 },
"claude-sonnet-4-20250514": { input: 3, output: 15 },
"claude-haiku-3-20250307": { input: 0.25, output: 1.25 },
"gpt-4o": { input: 2.5, output: 10 },
"gpt-4o-mini": { input: 0.15, output: 0.6 },
"gpt-4-turbo": { input: 10, output: 30 },
"o3-mini": { input: 1.1, output: 4.4 },
};
export function getModelCost(
modelId: string,
): { input: number; output: number } | undefined {
const direct = MODEL_COSTS[modelId];
if (direct) return direct;
for (const [key, cost] of Object.entries(MODEL_COSTS)) {
if (modelId.includes(key)) return cost;
}
return undefined;
}
/** Coerce arbitrary values into SDK-compatible `JsonValue`, stringifying unknowns. */
export function safeJsonValue(value: unknown): JsonValue {
if (value === null || value === undefined) return null;
if (
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean"
) {
return value;
}
if (Array.isArray(value)) {
return value.map((v) => safeJsonValue(v));
}
if (typeof value === "object") {
const result: Record<string, JsonValue> = {};
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
result[k] = safeJsonValue(v);
}
return result;
}
return String(value);
}