Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/empty-mayflies-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"dashboard": patch
"@gram/client": patch
"server": patch
---

Adds an initial pass "POC" implementation of Gram hooks for tool capture
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ package-lock.json
/examples/*/pnpm-lock.yaml
.worktrees
.granary/
.claude/hooks/*
13 changes: 13 additions & 0 deletions .mise-tasks/hooks/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

#MISE description="Test the Gram hooks Claude plugin locally"
#MISE dir="{{ config_root }}"

set -euo pipefail

echo "Starting Claude Code with Gram hooks plugin..."
echo ""
echo "Plugin directory: ./hooks/plugin-claude"
echo ""

exec claude --plugin-dir ./hooks/plugin-claude --debug
51 changes: 51 additions & 0 deletions .speakeasy/out.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14405,6 +14405,14 @@ components:
- tool_urn
- created_at
- updated_at
HookResult:
type: object
properties:
ok:
type: boolean
description: Whether the hook was received successfully
required:
- ok
InfoResponseBody:
type: object
properties:
Expand Down Expand Up @@ -15440,6 +15448,40 @@ components:
- credits
- included_credits
- has_active_subscription
PostToolUseFailurePayload:
type: object
properties:
tool_error:
description: The error from the tool
tool_input:
description: The input to the tool
tool_name:
type: string
description: The name of the tool that failed
required:
- tool_name
PostToolUsePayload:
type: object
properties:
tool_input:
description: The input to the tool
tool_name:
type: string
description: The name of the tool that was invoked
tool_response:
description: The response from the tool
required:
- tool_name
PreToolUsePayload:
type: object
properties:
tool_input:
description: The input to the tool
tool_name:
type: string
description: The name of the tool being invoked
required:
- tool_name
Project:
type: object
properties:
Expand Down Expand Up @@ -16767,6 +16809,9 @@ components:
ToolCallSummary:
type: object
properties:
event_source:
type: string
description: Event source (from attributes.gram.event.source)
gram_urn:
type: string
description: Gram URN associated with this tool call
Expand All @@ -16782,6 +16827,12 @@ components:
type: integer
description: Earliest log timestamp in Unix nanoseconds
format: int64
tool_name:
type: string
description: Tool name (from attributes.gram.tool.name)
tool_source:
type: string
description: Tool call source (from attributes.gram.tool_call.source)
trace_id:
type: string
description: Trace ID (32 hex characters)
Expand Down
24 changes: 13 additions & 11 deletions client/dashboard/src/pages/logs/TraceRow.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
ToolCallSummary,
TelemetryLogRecord,
ToolCallSummary,
} from "@gram/client/models/components";
import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";
import { StatusBadge } from "./StatusBadge";
import { TraceLogsList } from "./TraceLogsList";
import {
formatNanoTimestamp,
getStatusInfo,
getSourceFromUrn,
getToolNameFromUrn,
getStatusInfo,
getToolIcon,
getToolNameFromUrn,
} from "./utils";
import { TraceLogsList } from "./TraceLogsList";
import { StatusBadge } from "./StatusBadge";

interface TraceRowProps {
trace: ToolCallSummary;
Expand All @@ -27,9 +27,9 @@ export function TraceRow({
onLogClick,
}: TraceRowProps) {
const { isSuccess } = getStatusInfo(trace);
const sourceName = getSourceFromUrn(trace.gramUrn);
const toolName = getToolNameFromUrn(trace.gramUrn);
const ToolIcon = getToolIcon(trace.gramUrn);
const sourceName = trace.toolSource || getSourceFromUrn(trace.gramUrn);
const toolName = trace.toolName || getToolNameFromUrn(trace.gramUrn);
const ToolIcon = getToolIcon(trace);

return (
<div className="border-b border-border/50 last:border-b-0">
Expand All @@ -55,9 +55,11 @@ export function TraceRow({
{/* Icon + Source badge + Tool name */}
<div className="flex items-center gap-2 flex-1 min-w-0">
<ToolIcon className="size-4 shrink-0" strokeWidth={1.5} />
<span className="shrink-0 px-1.5 py-0.5 text-xs font-medium rounded bg-muted text-muted-foreground">
{sourceName}
</span>
{sourceName && (
<span className="shrink-0 px-1.5 py-0.5 text-xs font-medium rounded bg-muted text-muted-foreground">
{sourceName}
</span>
)}
<span className="text-sm font-mono truncate">{toolName}</span>
</div>

Expand Down
32 changes: 21 additions & 11 deletions client/dashboard/src/pages/logs/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { dateTimeFormatters } from "@/lib/dates";
import {
ToolCallSummary,
TelemetryLogRecord,
ToolCallSummary,
} from "@gram/client/models/components";
import { dateTimeFormatters } from "@/lib/dates";
import {
FileCode,
SquareTerminal as HookIcon,
LucideIcon,
PencilRuler,
SquareFunction,
LucideIcon,
Hammer as ToolIcon,
} from "lucide-react";

/**
Expand Down Expand Up @@ -98,16 +100,24 @@ export function getToolNameFromUrn(urn: string): string {
/**
* Get the appropriate icon for a tool based on its URN
*/
export function getToolIcon(urn: string): LucideIcon {
const { kind } = parseGramUrn(urn);
if (kind === "http") {
return FileCode;
export function getToolIcon(trace: ToolCallSummary): LucideIcon {
if (trace.gramUrn) {
const { kind } = parseGramUrn(trace.gramUrn);
if (kind === "http") {
return FileCode;
}
if (kind === "prompt") {
return PencilRuler;
}
// Otherwise it's a function tool
return SquareFunction;
}
if (kind === "prompt") {
return PencilRuler;

if (trace.eventSource === "hook") {
return HookIcon;
}
// Otherwise it's a function tool
return SquareFunction;

return ToolIcon;
}

/**
Expand Down
16 changes: 8 additions & 8 deletions client/sdk/.speakeasy/gen.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion client/sdk/.speakeasy/gen.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/sdk/jsr.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 4 additions & 10 deletions client/sdk/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions client/sdk/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function serverURLFromOptions(options: SDKOptions): URL | null {
export const SDK_METADATA = {
language: "typescript",
openapiDocVersion: "0.0.1",
sdkVersion: "0.27.9",
sdkVersion: "0.28.2",
genVersion: "2.801.2",
userAgent: "speakeasy-sdk/typescript 0.27.9 2.801.2 0.0.1 @gram/client",
userAgent: "speakeasy-sdk/typescript 0.28.2 2.801.2 0.0.1 @gram/client",
} as const;
18 changes: 18 additions & 0 deletions client/sdk/src/models/components/toolcallsummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { SDKValidationError } from "../errors/sdkvalidationerror.js";
* Summary information for a tool call
*/
export type ToolCallSummary = {
/**
* Event source (from attributes.gram.event.source)
*/
eventSource?: string | undefined;
/**
* Gram URN associated with this tool call
*/
Expand All @@ -28,6 +32,14 @@ export type ToolCallSummary = {
* Earliest log timestamp in Unix nanoseconds
*/
startTimeUnixNano: number;
/**
* Tool name (from attributes.gram.tool.name)
*/
toolName?: string | undefined;
/**
* Tool call source (from attributes.gram.tool_call.source)
*/
toolSource?: string | undefined;
/**
* Trace ID (32 hex characters)
*/
Expand All @@ -40,17 +52,23 @@ export const ToolCallSummary$inboundSchema: z.ZodType<
z.ZodTypeDef,
unknown
> = z.object({
event_source: z.string().optional(),
gram_urn: z.string(),
http_status_code: z.number().int().optional(),
log_count: z.number().int(),
start_time_unix_nano: z.number().int(),
tool_name: z.string().optional(),
tool_source: z.string().optional(),
trace_id: z.string(),
}).transform((v) => {
return remap$(v, {
"event_source": "eventSource",
"gram_urn": "gramUrn",
"http_status_code": "httpStatusCode",
"log_count": "logCount",
"start_time_unix_nano": "startTimeUnixNano",
"tool_name": "toolName",
"tool_source": "toolSource",
"trace_id": "traceId",
});
});
Expand Down
1 change: 1 addition & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ services:
restart: unless-stopped
ports:
- "${CLICKHOUSE_NATIVE_PORT}:9440"
- "${CLICKHOUSE_HTTP_PORT}:8123"
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: gram
Expand Down
Loading
Loading