Skip to content
Merged
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
1 change: 1 addition & 0 deletions scripts/integrations.test.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import './langsmith-page.test.mjs';
import './vercel-platform-page.test.mjs';
import './scalekit-page.test.mjs';
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
Expand Down
33 changes: 33 additions & 0 deletions scripts/scalekit-page.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { integrations } from '../src/lib/integrations.mjs';

const page = fileURLToPath(new URL('../src/pages/docs/integrations/frameworks/scalekit.mdx', import.meta.url));
test('Scalekit tool boundary has a discoverable, privacy-conscious tracing route', () => {
const entry = integrations.find(({ id }) => id === 'scalekit');
assert.equal(entry?.href, '/docs/integrations/frameworks/scalekit');
const source = readFileSync(page, 'utf8');
assert.match(source, /@scalekit-sdk\/node/);
assert.match(source, /@traceai\/fi-core/);
assert.match(source, /AsyncLocalStorageContextManager/);
assert.match(source, /context\.setGlobalContextManager/);
assert.match(source, /startActiveSpan/);
assert.match(source, /fi\.span\.kind.*TOOL/);
assert.match(source, /response\.data\?\.success === false/);
assert.match(source, /SpanStatusCode\.ERROR/);
assert.match(source, /finally\s*\{\s*span\.end\(\)/);
assert.doesNotMatch(source, /recordException|JSON\.stringify\(response|setAttribute\([^\n]*params/);
});

test('Scalekit guide links to live setup and does not promise broken batching', () => {
const source = readFileSync(page, 'utf8');
assert.match(source, /\[Observe project\]\(\/docs\/quickstart\/setup-observability\)/);
const example = source.match(/```javascript\n([\s\S]*?)\n```/)?.[1];
assert.ok(example);
assert.doesNotMatch(example, /batch:\s*true/);
assert.match(source, /SimpleSpanProcessor/);
assert.match(source, /30 concurrent exports/);
assert.match(source, /BatchSpanProcessor/);
});
1 change: 1 addition & 0 deletions src/lib/integrations.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const integrations = [
{ id: 'mastra', name: 'Mastra', href: '/docs/integrations/traceai/mastra', category: 'frameworks', summary: 'Trace TypeScript agents and workflows in Mastra.', method: 'traceAI SDK', logo: 'mastra' },
{ id: 'vercel-ai-sdk', name: 'Vercel AI SDK', href: '/docs/integrations/traceai/vercel', category: 'frameworks', summary: 'Capture model and tool calls from the Vercel AI SDK.', method: 'traceAI SDK', logo: 'vercel' },
{ id: 'vercel-platform', name: 'Vercel / Next.js', href: '/docs/integrations/runtimes/vercel', category: 'frameworks', summary: 'Export native Next.js request spans directly over OTLP/HTTP.', method: 'Native OTLP', logo: 'vercel', aliases: ['Vercel platform', 'Next.js', '@vercel/otel'] },
{ id: 'scalekit', name: 'Scalekit', href: '/docs/integrations/frameworks/scalekit', category: 'frameworks', summary: 'Trace Node.js executeTool calls as privacy-conscious tool spans.', method: 'OpenTelemetry', logo: 'generic-framework' },
{ id: 'tanstack-ai', name: 'TanStack AI', href: '/docs/integrations/frameworks/tanstack-ai', category: 'frameworks', summary: 'Send otelMiddleware chat, model-call and tool spans from Node.js.', method: 'OpenTelemetry', logo: 'generic-framework', aliases: ['TanStack'] },
{ id: 'agentscope', name: 'AgentScope', href: '/docs/integrations/frameworks/agentscope', category: 'frameworks', summary: 'Export native agent, model and tool spans.', method: 'OpenTelemetry', logo: 'agentscope' },
{ id: 'apollo-graphql', name: 'Apollo Server', href: '/docs/integrations/frameworks/apollo-graphql', category: 'frameworks', summary: 'Trace GraphQL operations and resolvers in Server 4.', method: 'OpenTelemetry', logo: 'apollo', aliases: ['Apollo GraphQL'] },
Expand Down
93 changes: 93 additions & 0 deletions src/pages/docs/integrations/frameworks/scalekit.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: "Scalekit tool tracing"
description: "Trace Scalekit Node.js executeTool calls as tool spans in Future AGI without recording account identifiers or payloads."
---

Trace each `scalekit.tools.executeTool()` call as a tool span under your application's active agent span in Future AGI. Scalekit's Node SDK does not emit these spans itself; this example instruments the **application boundary**, not a second wrapper around the same call.

## Install

Use Node.js 18.19+ or 20.6+ (not Node 19) and `@scalekit-sdk/node` 2.5.0 or later. The example also works with the current 2.21.0 SDK; earlier 2.x versions did not expose `tools.executeTool()`.

```bash
npm install @scalekit-sdk/node@^2.5.0 @traceai/fi-core@^1.1.0 @opentelemetry/api@^1.9.0 @opentelemetry/context-async-hooks@^2.0.1
```

Create an [Observe project](/docs/quickstart/setup-observability) and set the keys from [API keys](/docs/admin-settings/api-keys). Keep the Scalekit client secret on the server; never put it in browser code.

```bash
export FI_API_KEY="your-future-agi-api-key"
export FI_SECRET_KEY="your-future-agi-secret-key"
export FI_PROJECT_NAME="scalekit-tools"
export SCALEKIT_ENV_URL="your-scalekit-environment-url"
export SCALEKIT_CLIENT_ID="your-scalekit-client-id"
export SCALEKIT_CLIENT_SECRET="your-scalekit-client-secret"
```

The Future AGI provider sends OTLP/HTTP traces to `https://api.futureagi.com/tracer/v1/traces` with both Future AGI keys. For self-hosted Future AGI, set `FI_BASE_URL` to its origin (without `/tracer/v1/traces`).

## Trace a tool call

In your server application, register a provider once before running your agent, then trace the call at the place where your code awaits it:

```javascript
import { Scalekit } from "@scalekit-sdk/node";
import { register, ProjectType } from "@traceai/fi-core";
import { context, SpanStatusCode } from "@opentelemetry/api";
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";

// If the app already configured an OpenTelemetry context manager, keep it.
const manager = new AsyncLocalStorageContextManager().enable();
if (!context.setGlobalContextManager(manager)) manager.disable();

const provider = register({
projectName: process.env.FI_PROJECT_NAME,
projectType: ProjectType.OBSERVE,
});
const tracer = provider.getTracer("scalekit-tool-boundary");
const scalekit = new Scalekit(
process.env.SCALEKIT_ENV_URL,
process.env.SCALEKIT_CLIENT_ID,
process.env.SCALEKIT_CLIENT_SECRET,
);

async function executeScalekitTool({ toolName, connectedAccountId, params }) {
return tracer.startActiveSpan(`execute_tool ${toolName}`, async (span) => {
span.setAttribute("fi.span.kind", "TOOL");
span.setAttribute("tool.name", toolName);
try {
const response = await scalekit.tools.executeTool({
toolName, connectedAccountId, params,
});
// Scalekit returns arbitrary tool-specific data; adapt this predicate to
// your tool's response schema. A resolved failure is not an exception.
if (response.data?.success === false) {
span.setStatus({ code: SpanStatusCode.ERROR });
} else {
span.setStatus({ code: SpanStatusCode.OK });
}
return response;
} catch (error) {
// Do not attach error.message or stack: they may contain user data.
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
});
}

// Call executeScalekitTool() inside your active agent operation with the
// tool name, connected account ID, and parameters from your tool's schema.
// For a one-shot process, call await provider.shutdown() after the agent ends.
```

`@traceai/fi-core` 1.1.0 exports each span on completion through a `SimpleSpanProcessor`. Its `batch: true` option does not activate batching with OpenTelemetry SDK 2.x; do not rely on it for bursty production traffic. The default OTLP exporter allows 30 concurrent exports, so a slow collector can cause additional tool spans to be dropped. For sustained or bursty traffic, configure your existing OpenTelemetry SDK provider with a `BatchSpanProcessor` and an OTLP/HTTP exporter instead, preserving the Future AGI endpoint, API-key headers and Observe project resource attributes. Do not register both providers. For low-volume use of this example, keep the collector responsive and monitor for dropped exports.

Use `listTools()` to find the available tool name and its required parameters, then call `executeScalekitTool({ toolName, connectedAccountId, params })` from your agent's existing trace context. Scalekit returns `data` and `executionId`; `data` has **no universal success field**. The `success === false` check illustrates a tool whose payload uses that field: replace it with your tool's documented failure predicate so a resolved business error is marked as an error too. A thrown SDK/transport error is rethrown unchanged after marking the span.

Open **Observe**, choose `scalekit-tools` (or your `FI_PROJECT_NAME`), and open the trace containing the tool call. If you already have an OpenTelemetry provider and async context manager, use that provider's tracer and exporter; do not call `register()` or install a second context manager. Do not wrap `executeTool()` twice.

<Warning>
The tool span adds only the tool name, kind and error status, not tool arguments, results, connected-account IDs, credentials, execution IDs or exception messages. `register()` can still attach host/process details as resource attributes; use a provider with resource detection disabled or a redacting collector if those details are sensitive. Tool names can still disclose business operations; restrict names or redact them before exporting if needed. Your own agent or HTTP instrumentation can record additional fields independently. Do not assume `FI_HIDE_INPUTS` or `FI_HIDE_ERROR_DETAILS` sanitizes spans created with the standard OpenTelemetry API.
</Warning>
Loading