+
+ {BlockIcon && (
+
+ )}
{isExecuting ? (
{title}
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/layout.tsx b/apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/layout.tsx
new file mode 100644
index 00000000000..c19caf648a1
--- /dev/null
+++ b/apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/layout.tsx
@@ -0,0 +1,19 @@
+import {
+ SettingsHeaderProvider,
+ SettingsHeaderShell,
+} from '@/app/workspace/[workspaceId]/settings/components/settings-header/settings-header'
+
+/**
+ * Credit usage is a static route outside `[section]`, so it does not inherit
+ * `SettingsSectionLayout`'s chrome. `CreditUsageView` and its loading fallback
+ * render through `SettingsPanel`, which only registers header config into the
+ * `SettingsHeaderProvider` context — without this shell the page has no header
+ * bar, title, or scroll region.
+ */
+export default function CreditUsageLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/apps/sim/lib/copilot/tools/client/read-block.test.ts b/apps/sim/lib/copilot/tools/client/read-block.test.ts
new file mode 100644
index 00000000000..a3da77c0db5
--- /dev/null
+++ b/apps/sim/lib/copilot/tools/client/read-block.test.ts
@@ -0,0 +1,30 @@
+/**
+ * @vitest-environment node
+ */
+import { describe, expect, it, vi } from 'vitest'
+import { getReadTargetBlock } from '@/lib/copilot/tools/client/read-block'
+
+const gmailBlock = { type: 'gmail_v2', name: 'Gmail', icon: () => null }
+
+vi.mock('@/blocks/registry', () => ({
+ getBlock: vi.fn((type: string) => (type === 'gmail_v2' ? gmailBlock : undefined)),
+ getLatestBlock: vi.fn((baseType: string) => (baseType === 'gmail' ? gmailBlock : undefined)),
+}))
+
+describe('getReadTargetBlock', () => {
+ it('resolves a block schema read to its block', () => {
+ expect(getReadTargetBlock('components/blocks/gmail_v2.json')?.name).toBe('Gmail')
+ })
+
+ it('resolves integration operation and service-directory reads to the latest service block', () => {
+ expect(getReadTargetBlock('components/integrations/gmail/send.json')?.name).toBe('Gmail')
+ expect(getReadTargetBlock('components/integrations/gmail')?.name).toBe('Gmail')
+ })
+
+ it('returns undefined for unknown blocks and non-component paths', () => {
+ expect(getReadTargetBlock('components/blocks/unknown_block.json')).toBeUndefined()
+ expect(getReadTargetBlock('workflows/My Workflow/meta.json')).toBeUndefined()
+ expect(getReadTargetBlock('files/gmail_v2.json')).toBeUndefined()
+ expect(getReadTargetBlock(undefined)).toBeUndefined()
+ })
+})
diff --git a/apps/sim/lib/copilot/tools/client/read-block.ts b/apps/sim/lib/copilot/tools/client/read-block.ts
new file mode 100644
index 00000000000..c4faf4e8f8e
--- /dev/null
+++ b/apps/sim/lib/copilot/tools/client/read-block.ts
@@ -0,0 +1,23 @@
+import { getBlock, getLatestBlock } from '@/blocks/registry'
+import type { BlockConfig } from '@/blocks/types'
+
+/**
+ * Resolves the block a copilot `read` call targets when the path is a
+ * component schema — `components/blocks/{type}.json` or
+ * `components/integrations/{service}/{operation}.json` — so tool rows can show
+ * the block's display name and brand icon instead of the raw type id
+ * (e.g. "Gmail" instead of `gmail_v2`). Returns undefined for every other
+ * path, leaving the generic read-target labeling untouched.
+ */
+export function getReadTargetBlock(path: string | undefined): BlockConfig | undefined {
+ if (!path) return undefined
+ const segments = path.trim().split('/').filter(Boolean)
+ if (segments[0] !== 'components' || segments.length < 3) return undefined
+ if (segments[1] === 'blocks' && segments.length === 3) {
+ return getBlock(segments[2].replace(/\.json$/, ''))
+ }
+ if (segments[1] === 'integrations') {
+ return getLatestBlock(segments[2])
+ }
+ return undefined
+}
diff --git a/apps/sim/lib/copilot/tools/client/store-utils.test.ts b/apps/sim/lib/copilot/tools/client/store-utils.test.ts
index a8873d8bb4f..2fd6e54bcc6 100644
--- a/apps/sim/lib/copilot/tools/client/store-utils.test.ts
+++ b/apps/sim/lib/copilot/tools/client/store-utils.test.ts
@@ -2,11 +2,18 @@
* @vitest-environment node
*/
-import { describe, expect, it } from 'vitest'
+import { describe, expect, it, vi } from 'vitest'
import { Read as ReadTool } from '@/lib/copilot/generated/tool-catalog-v1'
import { resolveToolDisplay } from './store-utils'
import { ClientToolCallState } from './tool-call-state'
+const gmailBlock = { type: 'gmail_v2', name: 'Gmail', icon: () => null }
+
+vi.mock('@/blocks/registry', () => ({
+ getBlock: vi.fn((type: string) => (type === 'gmail_v2' ? gmailBlock : undefined)),
+ getLatestBlock: vi.fn((baseType: string) => (baseType === 'gmail' ? gmailBlock : undefined)),
+}))
+
describe('resolveToolDisplay', () => {
it('uses a friendly label for internal respond tools', () => {
expect(resolveToolDisplay('respond', ClientToolCallState.executing)?.text).toBe(
@@ -120,6 +127,26 @@ describe('resolveToolDisplay', () => {
).toBe('Read style details for deck.pptx')
})
+ it('shows the block display name for block and integration schema reads', () => {
+ expect(
+ resolveToolDisplay(ReadTool.id, ClientToolCallState.success, {
+ path: 'components/blocks/gmail_v2.json',
+ })?.text
+ ).toBe('Read Gmail')
+
+ expect(
+ resolveToolDisplay(ReadTool.id, ClientToolCallState.executing, {
+ path: 'components/integrations/gmail/send.json',
+ })?.text
+ ).toBe('Reading Gmail')
+
+ expect(
+ resolveToolDisplay(ReadTool.id, ClientToolCallState.success, {
+ path: 'components/blocks/unknown_block.json',
+ })?.text
+ ).toBe('Read unknown_block')
+ })
+
it('falls back to a humanized tool label for generic tools', () => {
expect(resolveToolDisplay('deploy_api', ClientToolCallState.success)?.text).toBe(
'Executed Deploy Api'
diff --git a/apps/sim/lib/copilot/tools/client/store-utils.ts b/apps/sim/lib/copilot/tools/client/store-utils.ts
index ec4c1190b2f..7364bbc1f87 100644
--- a/apps/sim/lib/copilot/tools/client/store-utils.ts
+++ b/apps/sim/lib/copilot/tools/client/store-utils.ts
@@ -4,6 +4,7 @@ import { FileText } from 'lucide-react'
import { Read as ReadTool } from '@/lib/copilot/generated/tool-catalog-v1'
import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resources/types'
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
+import { getReadTargetBlock } from '@/lib/copilot/tools/client/read-block'
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-call-state'
import { decodeVfsSegment } from '@/lib/copilot/vfs/path-utils'
@@ -98,6 +99,9 @@ function decodeVfsSegmentSafe(segment: string): string {
function describeReadTarget(path: string | undefined): string | undefined {
if (!path) return undefined
+ const block = getReadTargetBlock(path)
+ if (block) return block.name
+
const segments = path
.split('/')
.map((segment) => segment.trim())
diff --git a/apps/sim/vitest.setup.ts b/apps/sim/vitest.setup.ts
index b9a9ae7d14a..ca6b4fc3922 100644
--- a/apps/sim/vitest.setup.ts
+++ b/apps/sim/vitest.setup.ts
@@ -88,6 +88,7 @@ vi.mock('@/blocks/registry', () => ({
outputs: {},
})),
getAllBlocks: vi.fn(() => ({})),
+ getLatestBlock: vi.fn(() => undefined),
}))
vi.mock('@trigger.dev/sdk', () => ({