@moda-ai/cli 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/init/sdk-guide.d.ts +28 -1
- package/dist/init/sdk-guide.js +102 -98
- package/dist/init/sdk-integration.d.ts +23 -3
- package/dist/init/sdk-integration.js +106 -24
- package/dist/init/skill-resolver.d.ts +19 -0
- package/dist/init/skill-resolver.js +78 -0
- package/dist/skills-bundled.d.ts +35 -0
- package/dist/skills-bundled.js +157 -0
- package/dist/skills-manifest.d.ts +33 -0
- package/dist/skills-manifest.js +244 -0
- package/dist/skills.js +22 -142
- package/package.json +6 -4
- package/skills/integration/index.json +123 -0
- package/skills/integration/integration-node-anthropic/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-anthropic/SKILL.md +150 -0
- package/skills/integration/integration-node-generic/EXAMPLE.md +77 -0
- package/skills/integration/integration-node-generic/SKILL.md +170 -0
- package/skills/integration/integration-node-langchain/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-langchain/SKILL.md +144 -0
- package/skills/integration/integration-node-openai/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-openai/SKILL.md +148 -0
- package/skills/integration/integration-node-vercel-ai-sdk/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-vercel-ai-sdk/SKILL.md +179 -0
- package/skills/integration/integration-node-vercel-ai-sdk/references/prompt-attribution.md +31 -0
- package/skills/integration/integration-python-anthropic/EXAMPLE.md +66 -0
- package/skills/integration/integration-python-anthropic/SKILL.md +153 -0
- package/skills/integration/integration-python-claude-agent-sdk/EXAMPLE.md +61 -0
- package/skills/integration/integration-python-claude-agent-sdk/SKILL.md +129 -0
- package/skills/integration/integration-python-generic/EXAMPLE.md +46 -0
- package/skills/integration/integration-python-generic/SKILL.md +146 -0
- package/skills/integration/integration-python-langchain/EXAMPLE.md +63 -0
- package/skills/integration/integration-python-langchain/SKILL.md +141 -0
- package/skills/integration/integration-python-openai/EXAMPLE.md +65 -0
- package/skills/integration/integration-python-openai/SKILL.md +151 -0
- package/dist/init/detect.d.ts +0 -9
- package/dist/init/detect.js +0 -179
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "moda.skill_index.v1",
|
|
3
|
+
"bundled_at": "2026-07-06T02:25:00.333Z",
|
|
4
|
+
"cli_version": "1.12.0",
|
|
5
|
+
"skills": [
|
|
6
|
+
{
|
|
7
|
+
"id": "integration-node-anthropic",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"summary": "Auto-instrument @anthropic-ai/sdk client calls with Moda in a Node.js or TypeScript app; no per-call changes required.",
|
|
10
|
+
"targets": {
|
|
11
|
+
"language": "node",
|
|
12
|
+
"frameworks": [],
|
|
13
|
+
"providers": [
|
|
14
|
+
"anthropic"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "integration-node-generic",
|
|
20
|
+
"version": "1.0.0",
|
|
21
|
+
"summary": "General Moda SDK integration for a Node.js or TypeScript app when no specific framework or provider skill matches.",
|
|
22
|
+
"targets": {
|
|
23
|
+
"language": "node",
|
|
24
|
+
"frameworks": [],
|
|
25
|
+
"providers": []
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "integration-node-langchain",
|
|
30
|
+
"version": "1.0.0",
|
|
31
|
+
"summary": "Instrument a Node.js LangChain or LangGraph app with Moda: init before the framework, explicit conversation ids around every run.",
|
|
32
|
+
"targets": {
|
|
33
|
+
"language": "node",
|
|
34
|
+
"frameworks": [
|
|
35
|
+
"langchain"
|
|
36
|
+
],
|
|
37
|
+
"providers": []
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "integration-node-openai",
|
|
42
|
+
"version": "1.0.0",
|
|
43
|
+
"summary": "Auto-instrument direct openai client calls with Moda in a Node.js or TypeScript app; no per-call changes required.",
|
|
44
|
+
"targets": {
|
|
45
|
+
"language": "node",
|
|
46
|
+
"frameworks": [],
|
|
47
|
+
"providers": [
|
|
48
|
+
"openai"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "integration-node-vercel-ai-sdk",
|
|
54
|
+
"version": "1.0.0",
|
|
55
|
+
"summary": "Instrument Vercel AI SDK calls (generateText, streamText, generateObject) with Moda telemetry in a Node.js or TypeScript app.",
|
|
56
|
+
"targets": {
|
|
57
|
+
"language": "node",
|
|
58
|
+
"frameworks": [
|
|
59
|
+
"vercel-ai-sdk"
|
|
60
|
+
],
|
|
61
|
+
"providers": []
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "integration-python-anthropic",
|
|
66
|
+
"version": "1.0.0",
|
|
67
|
+
"summary": "Auto-instrument anthropic client calls with Moda in a Python app; no per-call changes required.",
|
|
68
|
+
"targets": {
|
|
69
|
+
"language": "python",
|
|
70
|
+
"frameworks": [],
|
|
71
|
+
"providers": [
|
|
72
|
+
"anthropic"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "integration-python-claude-agent-sdk",
|
|
78
|
+
"version": "1.0.0",
|
|
79
|
+
"summary": "Instrument Claude Agent SDK sessions (ClaudeSDKClient) with Moda via the dedicated moda-claude-agent-sdk instrumentor.",
|
|
80
|
+
"targets": {
|
|
81
|
+
"language": "python",
|
|
82
|
+
"frameworks": [
|
|
83
|
+
"claude-agent-sdk"
|
|
84
|
+
],
|
|
85
|
+
"providers": []
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "integration-python-generic",
|
|
90
|
+
"version": "1.0.0",
|
|
91
|
+
"summary": "General Moda SDK integration for a Python app when no specific framework or provider skill matches.",
|
|
92
|
+
"targets": {
|
|
93
|
+
"language": "python",
|
|
94
|
+
"frameworks": [],
|
|
95
|
+
"providers": []
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "integration-python-langchain",
|
|
100
|
+
"version": "1.0.0",
|
|
101
|
+
"summary": "Instrument a Python LangChain or LangGraph app with Moda: init before the framework, explicit conversation ids around every run.",
|
|
102
|
+
"targets": {
|
|
103
|
+
"language": "python",
|
|
104
|
+
"frameworks": [
|
|
105
|
+
"langchain"
|
|
106
|
+
],
|
|
107
|
+
"providers": []
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "integration-python-openai",
|
|
112
|
+
"version": "1.0.0",
|
|
113
|
+
"summary": "Auto-instrument openai client calls with Moda in a Python app; no per-call changes required.",
|
|
114
|
+
"targets": {
|
|
115
|
+
"language": "python",
|
|
116
|
+
"frameworks": [],
|
|
117
|
+
"providers": [
|
|
118
|
+
"openai"
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Golden reference: Express + Anthropic SDK + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal Express chat server that
|
|
4
|
+
calls the Anthropic SDK directly. Match this structure; adapt names to the
|
|
5
|
+
target repository.
|
|
6
|
+
|
|
7
|
+
## src/server.ts
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import express from 'express';
|
|
11
|
+
import { Moda, withContext } from 'moda-ai';
|
|
12
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
13
|
+
|
|
14
|
+
// Initialize Moda once, before the Anthropic client is created. After this,
|
|
15
|
+
// every messages.create / messages.stream call is captured automatically.
|
|
16
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
17
|
+
|
|
18
|
+
const client = new Anthropic();
|
|
19
|
+
const app = express();
|
|
20
|
+
app.use(express.json());
|
|
21
|
+
|
|
22
|
+
app.post('/chat', async (req, res) => {
|
|
23
|
+
const { sessionId, userId, message } = req.body;
|
|
24
|
+
|
|
25
|
+
// Scoped context: safe under concurrent requests.
|
|
26
|
+
const response = await withContext(`session_${sessionId}`, userId, async () =>
|
|
27
|
+
client.messages.create({
|
|
28
|
+
model: 'claude-sonnet-4-20250514',
|
|
29
|
+
max_tokens: 1024,
|
|
30
|
+
messages: [{ role: 'user', content: message }],
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const reply = response.content
|
|
35
|
+
.filter((block) => block.type === 'text')
|
|
36
|
+
.map((block) => block.text)
|
|
37
|
+
.join('');
|
|
38
|
+
res.json({ reply });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Flush pending telemetry on shutdown.
|
|
42
|
+
process.on('SIGTERM', async () => {
|
|
43
|
+
await Moda.flush();
|
|
44
|
+
await Moda.shutdown();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
app.listen(3000);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## What changed, and what did not
|
|
52
|
+
|
|
53
|
+
- `moda-ai` added to `package.json` dependencies.
|
|
54
|
+
- One `Moda.init` call added at startup, before `new Anthropic()`.
|
|
55
|
+
- The handler wraps its existing Anthropic call in `withContext`; the
|
|
56
|
+
`messages.create` call itself is unchanged — instrumentation is automatic.
|
|
57
|
+
- A `SIGTERM` handler flushes telemetry on shutdown.
|
|
58
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
59
|
+
runtime environment.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-node-anthropic
|
|
3
|
+
name: "Integrate Moda with the Anthropic SDK (Node.js / TypeScript)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: node
|
|
8
|
+
frameworks: []
|
|
9
|
+
providers:
|
|
10
|
+
- anthropic
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Auto-instrument @anthropic-ai/sdk client calls with Moda in a Node.js or TypeScript app; no per-call changes required."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the Anthropic SDK (Node.js / TypeScript)
|
|
16
|
+
|
|
17
|
+
Follow the steps below in order. Each step has a stop condition; when you hit
|
|
18
|
+
one, stop and report instead of guessing. `EXAMPLE.md` in this directory shows
|
|
19
|
+
the complete finished integration; match its structure. For API details beyond
|
|
20
|
+
this skill, read https://docs.moda.dev/ingestion/providers/anthropic and
|
|
21
|
+
https://docs.moda.dev/ingestion/moda-sdk-node (if the installed `moda-ai`
|
|
22
|
+
package ships docs under `node_modules/moda-ai/docs/`, prefer those; they
|
|
23
|
+
match the installed SDK version).
|
|
24
|
+
|
|
25
|
+
## Safety rules (apply to every step)
|
|
26
|
+
|
|
27
|
+
- Only touch the repository you were launched in.
|
|
28
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
29
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
30
|
+
environment or a secret manager.
|
|
31
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
32
|
+
- Do not commit unless the user asks.
|
|
33
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
34
|
+
guessing.
|
|
35
|
+
|
|
36
|
+
## Step 1: Confirm the raw Anthropic client is the integration surface
|
|
37
|
+
|
|
38
|
+
Find the real LLM call sites: `client.messages.create(...)` or
|
|
39
|
+
`client.messages.stream(...)` on a client constructed with `new Anthropic()`
|
|
40
|
+
from the `@anthropic-ai/sdk` package.
|
|
41
|
+
|
|
42
|
+
- If the Anthropic calls go through a wrapper framework instead — the `ai`
|
|
43
|
+
package (Vercel AI SDK) or LangChain — stop: instrument the wrapper layer
|
|
44
|
+
with its dedicated skill, never the underlying provider client.
|
|
45
|
+
- If the repo wraps its Anthropic client in a helper module (for example
|
|
46
|
+
`lib/anthropic.ts` exporting a shared client), plan to instrument at that
|
|
47
|
+
one place; do not edit every call site.
|
|
48
|
+
- If there are no Anthropic call sites at all, stop and report.
|
|
49
|
+
|
|
50
|
+
## Step 2: Install the SDK
|
|
51
|
+
|
|
52
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
53
|
+
(check the lockfile: `bun.lock`/`bun.lockb` means bun, `pnpm-lock.yaml` means
|
|
54
|
+
pnpm, `yarn.lock` means yarn, otherwise npm):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install moda-ai
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If `moda-ai` is already in `package.json`, skip the install.
|
|
61
|
+
|
|
62
|
+
## Step 3: Initialize Moda once at startup, before the client is created
|
|
63
|
+
|
|
64
|
+
Instrumentation is automatic: once `Moda.init` has run, every
|
|
65
|
+
`messages.create` / `messages.stream` call (including extended thinking and
|
|
66
|
+
tool use) is captured with no per-call changes.
|
|
67
|
+
|
|
68
|
+
`Moda.init` must run before the Anthropic client is constructed. If a
|
|
69
|
+
`Moda.init` call already exists, verify it runs at startup before client
|
|
70
|
+
creation and skip this step; never add a second call.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Moda } from 'moda-ai';
|
|
74
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
75
|
+
|
|
76
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
77
|
+
|
|
78
|
+
const client = new Anthropic();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If the client lives in a shared helper module, initialize Moda at the top of
|
|
82
|
+
the application entry point (before that module is imported), or at the top
|
|
83
|
+
of the helper module itself — whichever the existing structure makes
|
|
84
|
+
smallest.
|
|
85
|
+
|
|
86
|
+
## Step 4: Set conversation and user context
|
|
87
|
+
|
|
88
|
+
Set a stable conversation id before each LLM call so multi-turn
|
|
89
|
+
conversations group together, and a user id when one user can be identified.
|
|
90
|
+
|
|
91
|
+
For sequential code, use the global properties:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
Moda.conversationId = `session_${sessionId}`;
|
|
95
|
+
Moda.userId = userId;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For concurrent request handlers (any web server), use the scoped helper so
|
|
99
|
+
overlapping requests do not leak context into each other:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { withContext } from 'moda-ai';
|
|
103
|
+
|
|
104
|
+
const response = await withContext(`session_${sessionId}`, userId, async () =>
|
|
105
|
+
client.messages.create({
|
|
106
|
+
model: 'claude-sonnet-4-20250514',
|
|
107
|
+
max_tokens: 1024,
|
|
108
|
+
messages: [{ role: 'user', content: userMessage }],
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
114
|
+
identifier exists anywhere, the SDK falls back to hashing the first user
|
|
115
|
+
message plus the system prompt — acceptable only for simple chatbots that
|
|
116
|
+
pass full message history on every call. If this repo is an agent framework
|
|
117
|
+
or does not pass full history, an explicit conversation id is required; stop
|
|
118
|
+
and report if you cannot find a session identifier to use.
|
|
119
|
+
|
|
120
|
+
## Step 5: Flush before exit
|
|
121
|
+
|
|
122
|
+
For short-lived scripts and CLIs, flush before the process exits:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
await Moda.flush();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For long-running servers, flush on shutdown:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
process.on('SIGTERM', async () => {
|
|
132
|
+
await Moda.flush();
|
|
133
|
+
await Moda.shutdown();
|
|
134
|
+
process.exit(0);
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Step 6: Validate
|
|
139
|
+
|
|
140
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
141
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
142
|
+
verify telemetry reaches Moda:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
moda doctor --online --json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
149
|
+
is available in the runtime environment and that `Moda.init()` runs before
|
|
150
|
+
the Anthropic client is created.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Golden reference: generic Node.js app + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a small Node.js chat script whose LLM
|
|
4
|
+
calls go through the `openai` client (automatically instrumented), plus the
|
|
5
|
+
manual-tracing pattern for a provider Moda does not instrument. Match this
|
|
6
|
+
structure; adapt names to the target repository.
|
|
7
|
+
|
|
8
|
+
## src/main.ts
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { Moda, withContext } from 'moda-ai';
|
|
12
|
+
import OpenAI from 'openai';
|
|
13
|
+
|
|
14
|
+
// Initialize Moda once, before any LLM client is created.
|
|
15
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
16
|
+
|
|
17
|
+
const client = new OpenAI();
|
|
18
|
+
|
|
19
|
+
export async function answer(sessionId: string, userId: string, message: string) {
|
|
20
|
+
// Scoped context: safe under concurrent callers.
|
|
21
|
+
return withContext(`session_${sessionId}`, userId, async () =>
|
|
22
|
+
client.chat.completions.create({
|
|
23
|
+
model: 'gpt-4o',
|
|
24
|
+
messages: [{ role: 'user', content: message }],
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const response = await answer('demo-session', 'user-123', 'Hello!');
|
|
30
|
+
console.log(response.choices[0].message.content);
|
|
31
|
+
|
|
32
|
+
// Flush pending telemetry before exit.
|
|
33
|
+
await Moda.flush();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Manual tracing for a non-instrumented provider
|
|
37
|
+
|
|
38
|
+
Only needed when a call site does not go through an automatically
|
|
39
|
+
instrumented client:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { Moda } from 'moda-ai';
|
|
43
|
+
|
|
44
|
+
const messages = [{ role: 'user', content: 'Hello!' }];
|
|
45
|
+
|
|
46
|
+
const data = await Moda.withLLMCall(
|
|
47
|
+
{ vendor: 'my-provider', type: 'chat' },
|
|
48
|
+
async ({ span }) => {
|
|
49
|
+
span.reportRequest({ model: 'my-model', messages });
|
|
50
|
+
|
|
51
|
+
const response = await fetch('https://api.my-provider.example/v1/chat', {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({ model: 'my-model', messages }),
|
|
55
|
+
});
|
|
56
|
+
const body = await response.json();
|
|
57
|
+
|
|
58
|
+
span.reportResponse({
|
|
59
|
+
model: body.model,
|
|
60
|
+
usage: body.usage,
|
|
61
|
+
completions: body.choices,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return body;
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## What changed, and what did not
|
|
70
|
+
|
|
71
|
+
- `moda-ai` added to `package.json` dependencies.
|
|
72
|
+
- One `Moda.init` call added at startup, before any LLM client is created.
|
|
73
|
+
- Call sites through instrumented clients are unchanged; only context
|
|
74
|
+
scoping was added around them.
|
|
75
|
+
- Non-instrumented provider calls are wrapped in `Moda.withLLMCall`.
|
|
76
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
77
|
+
runtime environment.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-node-generic
|
|
3
|
+
name: "Integrate the Moda SDK (Node.js / TypeScript, any provider)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: node
|
|
8
|
+
frameworks: []
|
|
9
|
+
providers: []
|
|
10
|
+
sdk_package: moda-ai
|
|
11
|
+
summary: "General Moda SDK integration for a Node.js or TypeScript app when no specific framework or provider skill matches."
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Integrate the Moda SDK (Node.js / TypeScript, any provider)
|
|
15
|
+
|
|
16
|
+
This is the general Node.js integration path, used when no framework- or
|
|
17
|
+
provider-specific skill matches. Follow the steps below in order. Each step
|
|
18
|
+
has a stop condition; when you hit one, stop and report instead of guessing.
|
|
19
|
+
`EXAMPLE.md` in this directory shows a complete finished integration; match
|
|
20
|
+
its structure. For API details beyond this skill, read
|
|
21
|
+
https://docs.moda.dev/ingestion/moda-sdk-node (if the installed `moda-ai`
|
|
22
|
+
package ships docs under `node_modules/moda-ai/docs/`, prefer those; they
|
|
23
|
+
match the installed SDK version).
|
|
24
|
+
|
|
25
|
+
## Safety rules (apply to every step)
|
|
26
|
+
|
|
27
|
+
- Only touch the repository you were launched in.
|
|
28
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
29
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
30
|
+
environment or a secret manager.
|
|
31
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
32
|
+
- Do not commit unless the user asks.
|
|
33
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
34
|
+
guessing.
|
|
35
|
+
|
|
36
|
+
## Step 1: Find the real LLM call sites and pick the right surface
|
|
37
|
+
|
|
38
|
+
Search the repository for LLM calls and classify them:
|
|
39
|
+
|
|
40
|
+
- Calls through the `openai` or `@anthropic-ai/sdk` client: automatically
|
|
41
|
+
instrumented once Moda is initialized (Step 3); no per-call changes.
|
|
42
|
+
OpenAI-compatible gateways (for example OpenRouter used through the
|
|
43
|
+
`openai` client with a custom `baseURL`) count as `openai` here.
|
|
44
|
+
- Calls through the Vercel AI SDK (`ai` package) or LangChain: stop — use
|
|
45
|
+
the dedicated wrapper-framework skill instead; never instrument the
|
|
46
|
+
underlying provider client when a wrapper framework is in use.
|
|
47
|
+
- Direct HTTP calls to some other LLM provider: plan to trace them manually
|
|
48
|
+
with `Moda.withLLMCall` (Step 5).
|
|
49
|
+
|
|
50
|
+
If the repository has no LLM call sites at all, stop and report.
|
|
51
|
+
|
|
52
|
+
## Step 2: Install the SDK
|
|
53
|
+
|
|
54
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
55
|
+
(check the lockfile: `bun.lock`/`bun.lockb` means bun, `pnpm-lock.yaml` means
|
|
56
|
+
pnpm, `yarn.lock` means yarn, otherwise npm):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install moda-ai
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
If `moda-ai` is already in `package.json`, skip the install.
|
|
63
|
+
|
|
64
|
+
## Step 3: Initialize Moda once at startup
|
|
65
|
+
|
|
66
|
+
`Moda.init` must run once, at startup, before any LLM client is created. If
|
|
67
|
+
a `Moda.init` call already exists, verify it runs at startup and skip this
|
|
68
|
+
step; never add a second call.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Moda } from 'moda-ai';
|
|
72
|
+
|
|
73
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`Moda.init` is async; await it so instrumentation is active before the first
|
|
77
|
+
call.
|
|
78
|
+
|
|
79
|
+
## Step 4: Set conversation and user context
|
|
80
|
+
|
|
81
|
+
Set a stable conversation id before each LLM call so multi-turn
|
|
82
|
+
conversations group together, and a user id when one user can be identified.
|
|
83
|
+
If one user reaches the same agent through several channels, use the same
|
|
84
|
+
user id across channels.
|
|
85
|
+
|
|
86
|
+
For sequential code, use the global properties:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
Moda.conversationId = `session_${sessionId}`;
|
|
90
|
+
Moda.userId = userId;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For concurrent request handlers (any web server), use the scoped helper so
|
|
94
|
+
overlapping requests do not leak context into each other:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { withContext } from 'moda-ai';
|
|
98
|
+
|
|
99
|
+
const response = await withContext(`session_${sessionId}`, userId, async () =>
|
|
100
|
+
makeLlmCall(userMessage),
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
105
|
+
identifier exists anywhere, the SDK falls back to hashing the first user
|
|
106
|
+
message plus the system prompt — acceptable only for simple chatbots that
|
|
107
|
+
pass full message history on every call. If this repo is an agent framework
|
|
108
|
+
or does not pass full history, an explicit conversation id is required; stop
|
|
109
|
+
and report if you cannot find a session identifier to use.
|
|
110
|
+
|
|
111
|
+
## Step 5: Manually trace non-instrumented providers (only if found in Step 1)
|
|
112
|
+
|
|
113
|
+
For providers that are not automatically instrumented (direct HTTP calls,
|
|
114
|
+
custom providers), wrap the call with `Moda.withLLMCall`:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { Moda } from 'moda-ai';
|
|
118
|
+
|
|
119
|
+
const result = await Moda.withLLMCall(
|
|
120
|
+
{ vendor: 'my-provider', type: 'chat' },
|
|
121
|
+
async ({ span }) => {
|
|
122
|
+
span.reportRequest({ model: 'my-model', messages });
|
|
123
|
+
|
|
124
|
+
const response = await callProviderHttpApi(messages);
|
|
125
|
+
|
|
126
|
+
span.reportResponse({
|
|
127
|
+
model: response.model,
|
|
128
|
+
usage: response.usage,
|
|
129
|
+
completions: response.choices,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return response;
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Skip this step entirely when every call site goes through an automatically
|
|
138
|
+
instrumented client.
|
|
139
|
+
|
|
140
|
+
## Step 6: Flush before exit
|
|
141
|
+
|
|
142
|
+
For short-lived scripts and CLIs, flush before the process exits:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
await Moda.flush();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For long-running servers, flush on shutdown:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
process.on('SIGTERM', async () => {
|
|
152
|
+
await Moda.flush();
|
|
153
|
+
await Moda.shutdown();
|
|
154
|
+
process.exit(0);
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Step 7: Validate
|
|
159
|
+
|
|
160
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
161
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
162
|
+
verify telemetry reaches Moda:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
moda doctor --online --json
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
169
|
+
is available in the runtime environment and that `Moda.init()` runs before
|
|
170
|
+
the first LLM client is created.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Golden reference: Node.js LangChain agent + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal LangChain app. Match this
|
|
4
|
+
structure; adapt names to the target repository. Note what is absent: no
|
|
5
|
+
LangChain callbacks or tracer classes for Moda — instrumentation happens at
|
|
6
|
+
the Moda SDK level, and the only per-run change is the explicit conversation
|
|
7
|
+
id wrapper.
|
|
8
|
+
|
|
9
|
+
## src/agent.ts
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { Moda, withContext } from 'moda-ai';
|
|
13
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
14
|
+
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
|
15
|
+
|
|
16
|
+
// Initialize Moda once, before LangChain constructs any provider client,
|
|
17
|
+
// so instrumentation is active for everything the agent does.
|
|
18
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
19
|
+
|
|
20
|
+
const model = new ChatOpenAI({ model: 'gpt-4o' });
|
|
21
|
+
const prompt = ChatPromptTemplate.fromMessages([
|
|
22
|
+
['system', 'You are a helpful assistant.'],
|
|
23
|
+
['human', '{input}'],
|
|
24
|
+
]);
|
|
25
|
+
const chain = prompt.pipe(model);
|
|
26
|
+
|
|
27
|
+
export async function runAgent(sessionId: string, userId: string, input: string) {
|
|
28
|
+
// Explicit conversation id around the whole run: every internal LLM call
|
|
29
|
+
// made during this invocation shares it. Automatic detection does NOT
|
|
30
|
+
// work for agent frameworks.
|
|
31
|
+
return withContext(`agent_session_${sessionId}`, userId, async () =>
|
|
32
|
+
chain.invoke({ input }),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## src/main.ts
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Moda } from 'moda-ai';
|
|
41
|
+
import { runAgent } from './agent.js';
|
|
42
|
+
|
|
43
|
+
const result = await runAgent('demo-session', 'user-123', 'What is TypeScript?');
|
|
44
|
+
console.log(result.content);
|
|
45
|
+
|
|
46
|
+
// Flush pending telemetry before exit.
|
|
47
|
+
await Moda.flush();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## What changed, and what did not
|
|
51
|
+
|
|
52
|
+
- `moda-ai` added to `package.json` dependencies.
|
|
53
|
+
- One `Moda.init` call added at startup, before any LangChain model is
|
|
54
|
+
constructed.
|
|
55
|
+
- Each agent/chain entry point is wrapped in `withContext` with an explicit
|
|
56
|
+
conversation id; the LangChain code itself is unchanged.
|
|
57
|
+
- No LangChain callbacks, handlers, or tracers were added for Moda.
|
|
58
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
59
|
+
runtime environment.
|