@moda-ai/cli 1.11.0 → 1.12.2
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/cli.js +5 -0
- package/dist/harness-remote.d.ts +33 -0
- package/dist/harness-remote.js +203 -0
- package/dist/harness.d.ts +75 -0
- package/dist/harness.js +92 -37
- package/dist/init/index.js +14 -4
- 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,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-node-langchain
|
|
3
|
+
name: "Integrate Moda with LangChain / LangGraph (Node.js / TypeScript)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: node
|
|
8
|
+
frameworks:
|
|
9
|
+
- langchain
|
|
10
|
+
providers: []
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Instrument a Node.js LangChain or LangGraph app with Moda: init before the framework, explicit conversation ids around every run."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with LangChain / LangGraph (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/moda-sdk-node (if the
|
|
21
|
+
installed `moda-ai` package ships docs under `node_modules/moda-ai/docs/`,
|
|
22
|
+
prefer those; they match the installed SDK version).
|
|
23
|
+
|
|
24
|
+
## Safety rules (apply to every step)
|
|
25
|
+
|
|
26
|
+
- Only touch the repository you were launched in.
|
|
27
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
28
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
29
|
+
environment or a secret manager.
|
|
30
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
31
|
+
- Do not commit unless the user asks.
|
|
32
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
33
|
+
guessing.
|
|
34
|
+
|
|
35
|
+
## What is different about agent frameworks
|
|
36
|
+
|
|
37
|
+
Two constraints drive every step below:
|
|
38
|
+
|
|
39
|
+
1. Instrument at the Moda SDK level, not inside LangChain. Initialize Moda
|
|
40
|
+
once and set an explicit conversation id around each run; the LLM calls
|
|
41
|
+
the agent makes internally are then captured and share that id (this is
|
|
42
|
+
the documented agent-framework pattern — see the "Solution for Agent
|
|
43
|
+
Applications" section of the Node SDK docs). Do not add LangChain
|
|
44
|
+
callbacks, handlers, or tracer classes for Moda — no such Moda API
|
|
45
|
+
exists.
|
|
46
|
+
2. Explicit conversation ids are mandatory. Moda's automatic conversation
|
|
47
|
+
detection hashes the first user message, and agent frameworks construct
|
|
48
|
+
different messages on every internal iteration, so without an explicit id
|
|
49
|
+
every iteration lands in a different conversation.
|
|
50
|
+
|
|
51
|
+
## Step 1: Find the agent/chain entry points
|
|
52
|
+
|
|
53
|
+
Find where the application invokes LangChain or LangGraph: `.invoke(...)`,
|
|
54
|
+
`.stream(...)`, agent executor runs, or graph runs. These entry points — not
|
|
55
|
+
the internal LLM calls — are where conversation context gets set.
|
|
56
|
+
|
|
57
|
+
- If you cannot find any LangChain/LangGraph entry point, stop and report.
|
|
58
|
+
- If the repository's LLM calls actually go through the Vercel AI SDK (the
|
|
59
|
+
`ai` package) rather than LangChain, stop: use the Vercel AI SDK skill
|
|
60
|
+
instead.
|
|
61
|
+
|
|
62
|
+
## Step 2: Install the SDK
|
|
63
|
+
|
|
64
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
65
|
+
(check the lockfile: `bun.lock`/`bun.lockb` means bun, `pnpm-lock.yaml` means
|
|
66
|
+
pnpm, `yarn.lock` means yarn, otherwise npm):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install moda-ai
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If `moda-ai` is already in `package.json`, skip the install.
|
|
73
|
+
|
|
74
|
+
## Step 3: Initialize Moda once at startup
|
|
75
|
+
|
|
76
|
+
`Moda.init` must run before LangChain constructs any provider client (which
|
|
77
|
+
usually happens at model/agent construction). Initialize at the top of the
|
|
78
|
+
application entry point. If a `Moda.init` call already exists, verify it runs
|
|
79
|
+
at startup and skip this step; never add a second call.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { Moda } from 'moda-ai';
|
|
83
|
+
|
|
84
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Step 4: Wrap every agent/chain run in an explicit conversation id
|
|
88
|
+
|
|
89
|
+
Wrap each entry point found in Step 1 so all internal LLM calls made during
|
|
90
|
+
that run share one conversation id. Use the scoped helpers so concurrent
|
|
91
|
+
runs do not leak context into each other:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { withContext } from 'moda-ai';
|
|
95
|
+
|
|
96
|
+
const result = await withContext(`agent_session_${sessionId}`, userId, async () =>
|
|
97
|
+
agent.invoke({ input: userQuery }),
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For strictly sequential code, the global property form is equivalent:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
Moda.conversationId = `agent_session_${sessionId}`;
|
|
105
|
+
const result = await agent.invoke({ input: userQuery });
|
|
106
|
+
Moda.conversationId = null;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Reuse the session/thread identifier the app already has (LangGraph
|
|
110
|
+
`thread_id`, a chat session id, a ticket id). If no session identifier
|
|
111
|
+
exists anywhere, stop and report; do not invent one.
|
|
112
|
+
|
|
113
|
+
## Step 5: Flush before exit
|
|
114
|
+
|
|
115
|
+
For short-lived scripts and CLIs, flush before the process exits:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
await Moda.flush();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For long-running servers, flush on shutdown:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
process.on('SIGTERM', async () => {
|
|
125
|
+
await Moda.flush();
|
|
126
|
+
await Moda.shutdown();
|
|
127
|
+
process.exit(0);
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Step 6: Validate
|
|
132
|
+
|
|
133
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
134
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
135
|
+
verify telemetry reaches Moda:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
moda doctor --online --json
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The report should show recent traces, and multi-step agent runs should share
|
|
142
|
+
one conversation id. If each internal LLM call shows up as its own
|
|
143
|
+
conversation, the explicit conversation id from Step 4 is not wrapping the
|
|
144
|
+
run — fix that before finishing.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Golden reference: Express + OpenAI SDK + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal Express chat server that
|
|
4
|
+
calls the OpenAI 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 OpenAI from 'openai';
|
|
13
|
+
|
|
14
|
+
// Initialize Moda once, before the OpenAI client is created. After this,
|
|
15
|
+
// every chat.completions.create call is captured automatically.
|
|
16
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
17
|
+
|
|
18
|
+
const client = new OpenAI();
|
|
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.chat.completions.create({
|
|
28
|
+
model: 'gpt-4o',
|
|
29
|
+
messages: [{ role: 'user', content: message }],
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
res.json({ reply: response.choices[0].message.content });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Flush pending telemetry on shutdown.
|
|
37
|
+
process.on('SIGTERM', async () => {
|
|
38
|
+
await Moda.flush();
|
|
39
|
+
await Moda.shutdown();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.listen(3000);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What changed, and what did not
|
|
47
|
+
|
|
48
|
+
- `moda-ai` added to `package.json` dependencies.
|
|
49
|
+
- One `Moda.init` call added at startup, before `new OpenAI()`.
|
|
50
|
+
- The handler wraps its existing OpenAI call in `withContext`; the
|
|
51
|
+
`chat.completions.create` call itself is unchanged — instrumentation is
|
|
52
|
+
automatic.
|
|
53
|
+
- A `SIGTERM` handler flushes telemetry on shutdown.
|
|
54
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
55
|
+
runtime environment.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-node-openai
|
|
3
|
+
name: "Integrate Moda with the OpenAI SDK (Node.js / TypeScript)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: node
|
|
8
|
+
frameworks: []
|
|
9
|
+
providers:
|
|
10
|
+
- openai
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Auto-instrument direct openai client calls with Moda in a Node.js or TypeScript app; no per-call changes required."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the OpenAI 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/openai 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 OpenAI client is the integration surface
|
|
37
|
+
|
|
38
|
+
Find the real LLM call sites: `client.chat.completions.create(...)` on a
|
|
39
|
+
client constructed with `new OpenAI()` from the `openai` package.
|
|
40
|
+
|
|
41
|
+
- If the OpenAI calls go through a wrapper framework instead — the `ai`
|
|
42
|
+
package (Vercel AI SDK) or LangChain — stop: instrument the wrapper layer
|
|
43
|
+
with its dedicated skill, never the underlying provider client.
|
|
44
|
+
- If the repo wraps its OpenAI client in a helper module (for example
|
|
45
|
+
`lib/openai.ts` exporting a shared client), plan to instrument at that one
|
|
46
|
+
place; do not edit every call site.
|
|
47
|
+
- If there are no OpenAI call sites at all, stop and report.
|
|
48
|
+
|
|
49
|
+
## Step 2: Install the SDK
|
|
50
|
+
|
|
51
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
52
|
+
(check the lockfile: `bun.lock`/`bun.lockb` means bun, `pnpm-lock.yaml` means
|
|
53
|
+
pnpm, `yarn.lock` means yarn, otherwise npm):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install moda-ai
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If `moda-ai` is already in `package.json`, skip the install.
|
|
60
|
+
|
|
61
|
+
## Step 3: Initialize Moda once at startup, before the client is created
|
|
62
|
+
|
|
63
|
+
Instrumentation is automatic: once `Moda.init` has run, every
|
|
64
|
+
`chat.completions.create` call (including streaming, function calling, and
|
|
65
|
+
embeddings) is captured with no per-call changes.
|
|
66
|
+
|
|
67
|
+
`Moda.init` must run before the OpenAI client is constructed. If a
|
|
68
|
+
`Moda.init` call already exists, verify it runs at startup before client
|
|
69
|
+
creation and skip this step; never add a second call.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { Moda } from 'moda-ai';
|
|
73
|
+
import OpenAI from 'openai';
|
|
74
|
+
|
|
75
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
76
|
+
|
|
77
|
+
const client = new OpenAI();
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
If the client lives in a shared helper module, initialize Moda at the top of
|
|
81
|
+
the application entry point (before that module is imported), or at the top
|
|
82
|
+
of the helper module itself — whichever the existing structure makes
|
|
83
|
+
smallest.
|
|
84
|
+
|
|
85
|
+
## Step 4: Set conversation and user context
|
|
86
|
+
|
|
87
|
+
Set a stable conversation id before each LLM call so multi-turn
|
|
88
|
+
conversations group together, and a user id when one user can be identified.
|
|
89
|
+
|
|
90
|
+
For sequential code, use the global properties:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
Moda.conversationId = `session_${sessionId}`;
|
|
94
|
+
Moda.userId = userId;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For concurrent request handlers (any web server), use the scoped helper so
|
|
98
|
+
overlapping requests do not leak context into each other:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { withContext } from 'moda-ai';
|
|
102
|
+
|
|
103
|
+
const response = await withContext(`session_${sessionId}`, userId, async () =>
|
|
104
|
+
client.chat.completions.create({
|
|
105
|
+
model: 'gpt-4o',
|
|
106
|
+
messages: [{ role: 'user', content: userMessage }],
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
112
|
+
identifier exists anywhere, the SDK falls back to hashing the first user
|
|
113
|
+
message plus the system prompt — acceptable only for simple chatbots that
|
|
114
|
+
pass full message history on every call. If this repo is an agent framework
|
|
115
|
+
or does not pass full history, an explicit conversation id is required; stop
|
|
116
|
+
and report if you cannot find a session identifier to use.
|
|
117
|
+
|
|
118
|
+
## Step 5: Flush before exit
|
|
119
|
+
|
|
120
|
+
For short-lived scripts and CLIs, flush before the process exits:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
await Moda.flush();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For long-running servers, flush on shutdown:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
process.on('SIGTERM', async () => {
|
|
130
|
+
await Moda.flush();
|
|
131
|
+
await Moda.shutdown();
|
|
132
|
+
process.exit(0);
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Step 6: Validate
|
|
137
|
+
|
|
138
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
139
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
140
|
+
verify telemetry reaches Moda:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
moda doctor --online --json
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
147
|
+
is available in the runtime environment and that `Moda.init()` runs before
|
|
148
|
+
the OpenAI client is created.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Golden reference: Next.js App Router + Vercel AI SDK + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal Next.js chat route. Match
|
|
4
|
+
this structure; adapt names to the target repository.
|
|
5
|
+
|
|
6
|
+
## instrumentation.ts (project root)
|
|
7
|
+
|
|
8
|
+
Moda initializes once at server startup, before any AI SDK call.
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
export async function register() {
|
|
12
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
13
|
+
const { Moda } = await import('moda-ai');
|
|
14
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## app/api/chat/route.ts
|
|
20
|
+
|
|
21
|
+
Every AI SDK call passes `experimental_telemetry`, and conversation/user
|
|
22
|
+
context is scoped per request with `withContext` so concurrent requests
|
|
23
|
+
never leak into each other.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Moda, withContext } from 'moda-ai';
|
|
27
|
+
import { generateText } from 'ai';
|
|
28
|
+
import { openai } from '@ai-sdk/openai';
|
|
29
|
+
|
|
30
|
+
export async function POST(request: Request) {
|
|
31
|
+
const { sessionId, userId, message } = await request.json();
|
|
32
|
+
|
|
33
|
+
const result = await withContext(`session_${sessionId}`, userId, async () =>
|
|
34
|
+
generateText({
|
|
35
|
+
model: openai('gpt-4o'),
|
|
36
|
+
prompt: message,
|
|
37
|
+
experimental_telemetry: Moda.getVercelAITelemetry(),
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
await Moda.flush();
|
|
42
|
+
|
|
43
|
+
return Response.json({ reply: result.text });
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## What changed, and what did not
|
|
48
|
+
|
|
49
|
+
- `moda-ai` added to `package.json` dependencies.
|
|
50
|
+
- `instrumentation.ts` created (or extended) with the one-time `Moda.init`.
|
|
51
|
+
- Each AI SDK call site gained `experimental_telemetry` and scoped context.
|
|
52
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
53
|
+
runtime environment.
|
|
54
|
+
- The raw `@ai-sdk/openai` provider client was NOT separately instrumented;
|
|
55
|
+
the AI SDK telemetry layer is the single integration surface.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-node-vercel-ai-sdk
|
|
3
|
+
name: "Integrate Moda with the Vercel AI SDK (Node.js / TypeScript)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: node
|
|
8
|
+
frameworks:
|
|
9
|
+
- vercel-ai-sdk
|
|
10
|
+
providers: []
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Instrument Vercel AI SDK calls (generateText, streamText, generateObject) with Moda telemetry in a Node.js or TypeScript app."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the Vercel AI 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/vercel-ai-sdk (if the
|
|
21
|
+
installed `moda-ai` package ships docs under `node_modules/moda-ai/docs/`,
|
|
22
|
+
prefer those; they match the installed SDK version).
|
|
23
|
+
|
|
24
|
+
## Safety rules (apply to every step)
|
|
25
|
+
|
|
26
|
+
- Only touch the repository you were launched in.
|
|
27
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
28
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
29
|
+
environment or a secret manager.
|
|
30
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
31
|
+
- Do not commit unless the user asks.
|
|
32
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
33
|
+
guessing.
|
|
34
|
+
|
|
35
|
+
## Step 1: Confirm the Vercel AI SDK is the integration surface
|
|
36
|
+
|
|
37
|
+
Find the real LLM call sites: search for `generateText`, `streamText`,
|
|
38
|
+
`generateObject`, or `streamObject` imported from the `ai` package.
|
|
39
|
+
|
|
40
|
+
- If you find none, stop and report: this skill only applies to apps that
|
|
41
|
+
call the Vercel AI SDK directly.
|
|
42
|
+
- Instrument the Vercel AI SDK layer only. Do NOT also instrument raw
|
|
43
|
+
`openai` / `@anthropic-ai/sdk` clients that are used through the AI SDK;
|
|
44
|
+
that produces duplicate telemetry for the same call.
|
|
45
|
+
|
|
46
|
+
## Step 2: Install the SDK
|
|
47
|
+
|
|
48
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
49
|
+
(check the lockfile: `bun.lock`/`bun.lockb` means bun, `pnpm-lock.yaml` means
|
|
50
|
+
pnpm, `yarn.lock` means yarn, otherwise npm):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install moda-ai
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If `moda-ai` is already in `package.json`, skip the install.
|
|
57
|
+
|
|
58
|
+
## Step 3: Initialize Moda once at startup
|
|
59
|
+
|
|
60
|
+
`Moda.init` must run once, before the first AI SDK call. If a `Moda.init`
|
|
61
|
+
call already exists somewhere in the codebase, verify it runs at startup and
|
|
62
|
+
skip this step; never add a second call.
|
|
63
|
+
|
|
64
|
+
Next.js: put it in `instrumentation.ts` at the project root (create the file
|
|
65
|
+
if it does not exist):
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
export async function register() {
|
|
69
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
70
|
+
const { Moda } = await import('moda-ai');
|
|
71
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Any other Node app: initialize at the top of the entry point, before the
|
|
77
|
+
first AI SDK call:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { Moda } from 'moda-ai';
|
|
81
|
+
|
|
82
|
+
await Moda.init(process.env.MODA_API_KEY!);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`Moda.init` is async. Await it so initialization completes before the first
|
|
86
|
+
LLM call; skipping the await risks the first call going uninstrumented.
|
|
87
|
+
|
|
88
|
+
## Step 4: Pass Moda telemetry at every AI SDK call site
|
|
89
|
+
|
|
90
|
+
Add `experimental_telemetry: Moda.getVercelAITelemetry()` to every
|
|
91
|
+
`generateText` / `streamText` / `generateObject` / `streamObject` call:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { Moda } from 'moda-ai';
|
|
95
|
+
import { generateText } from 'ai';
|
|
96
|
+
import { openai } from '@ai-sdk/openai';
|
|
97
|
+
|
|
98
|
+
const result = await generateText({
|
|
99
|
+
model: openai('gpt-4o'),
|
|
100
|
+
prompt: userMessage,
|
|
101
|
+
experimental_telemetry: Moda.getVercelAITelemetry(),
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Streaming works the same way; telemetry is captured after the stream
|
|
106
|
+
completes. If a call site already passes `experimental_telemetry`, stop and
|
|
107
|
+
report that call site instead of overwriting it.
|
|
108
|
+
|
|
109
|
+
## Step 5: Set conversation and user context
|
|
110
|
+
|
|
111
|
+
Set a stable conversation id before each AI SDK call so multi-turn
|
|
112
|
+
conversations group together, and a user id when one user can be identified.
|
|
113
|
+
Set them before calling `Moda.getVercelAITelemetry()`; the telemetry config
|
|
114
|
+
captures them at call time.
|
|
115
|
+
|
|
116
|
+
For sequential code, use the global properties:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
Moda.conversationId = `session_${sessionId}`;
|
|
120
|
+
Moda.userId = userId;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For concurrent request handlers (any web server), use the scoped helper so
|
|
124
|
+
overlapping requests do not leak context into each other:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { Moda, withContext } from 'moda-ai';
|
|
128
|
+
|
|
129
|
+
const result = await withContext(`session_${sessionId}`, userId, async () =>
|
|
130
|
+
generateText({
|
|
131
|
+
model: openai('gpt-4o'),
|
|
132
|
+
prompt: userMessage,
|
|
133
|
+
experimental_telemetry: Moda.getVercelAITelemetry(),
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
139
|
+
identifier exists anywhere, stop and report; do not invent one.
|
|
140
|
+
|
|
141
|
+
## Step 6: Attribute managed prompts (only if the repo uses them)
|
|
142
|
+
|
|
143
|
+
Only if this repository has a `.moda/prompts.yml` manifest: render managed
|
|
144
|
+
prompts through the SDK before creating the telemetry config, so traces point
|
|
145
|
+
at exact prompt versions. See
|
|
146
|
+
[references/prompt-attribution.md](references/prompt-attribution.md).
|
|
147
|
+
If there is no prompt manifest, skip this step.
|
|
148
|
+
|
|
149
|
+
## Step 7: Flush before exit
|
|
150
|
+
|
|
151
|
+
For short-lived scripts and CLIs, flush before the process exits:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
await Moda.flush();
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
For long-running servers, flush on shutdown:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
process.on('SIGTERM', async () => {
|
|
161
|
+
await Moda.flush();
|
|
162
|
+
await Moda.shutdown();
|
|
163
|
+
process.exit(0);
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Step 8: Validate
|
|
168
|
+
|
|
169
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
170
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
171
|
+
verify telemetry reaches Moda:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
moda doctor --online --json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
178
|
+
is available in the runtime environment and that `Moda.init()` runs before
|
|
179
|
+
the first AI SDK call.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Prompt attribution with the Vercel AI SDK
|
|
2
|
+
|
|
3
|
+
Only relevant when the repository manages prompts with Moda
|
|
4
|
+
(`.moda/prompts.yml` exists and prompts are synced with `moda prompts sync`).
|
|
5
|
+
|
|
6
|
+
Render the managed prompt through the SDK, then create the telemetry config.
|
|
7
|
+
`Moda.getVercelAITelemetry()` includes the rendered prompt's metadata
|
|
8
|
+
(`moda.prompt_key`, `moda.prompt_id`, `moda.prompt_version`,
|
|
9
|
+
`moda.prompt_version_id`) in `experimental_telemetry.metadata`, so production
|
|
10
|
+
traces point at the exact prompt version:
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { Moda } from 'moda-ai';
|
|
14
|
+
import { generateText } from 'ai';
|
|
15
|
+
import { openai } from '@ai-sdk/openai';
|
|
16
|
+
|
|
17
|
+
const rendered = Moda.prompt('support.triage').render({
|
|
18
|
+
ticket: { text: userMessage },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const result = await generateText({
|
|
22
|
+
model: openai('gpt-4o'),
|
|
23
|
+
messages: rendered.messages,
|
|
24
|
+
experimental_telemetry: Moda.getVercelAITelemetry(),
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Order matters: render the prompt before calling
|
|
29
|
+
`Moda.getVercelAITelemetry()` so prompt metadata is attached.
|
|
30
|
+
|
|
31
|
+
Full reference: https://docs.moda.dev/ingestion/vercel-ai-sdk
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Golden reference: FastAPI + Anthropic SDK + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal FastAPI chat service that
|
|
4
|
+
calls the Anthropic SDK directly. Match this structure; adapt names to the
|
|
5
|
+
target repository.
|
|
6
|
+
|
|
7
|
+
## app/main.py
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import os
|
|
11
|
+
from contextlib import asynccontextmanager
|
|
12
|
+
|
|
13
|
+
import moda
|
|
14
|
+
from anthropic import Anthropic
|
|
15
|
+
from fastapi import FastAPI
|
|
16
|
+
from moda import set_conversation_id, set_user_id
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
|
|
19
|
+
# Initialize Moda before creating any LLM client so instrumentation is
|
|
20
|
+
# active. After this, every messages.create call is captured automatically.
|
|
21
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
22
|
+
|
|
23
|
+
client = Anthropic()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@asynccontextmanager
|
|
27
|
+
async def lifespan(app: FastAPI):
|
|
28
|
+
yield
|
|
29
|
+
# Flush pending telemetry on shutdown.
|
|
30
|
+
moda.flush()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
app = FastAPI(lifespan=lifespan)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ChatRequest(BaseModel):
|
|
37
|
+
session_id: str
|
|
38
|
+
user_id: str
|
|
39
|
+
message: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.post("/chat")
|
|
43
|
+
def chat(request: ChatRequest):
|
|
44
|
+
# Scoped context: safe under concurrent requests.
|
|
45
|
+
with set_conversation_id(f"session_{request.session_id}"):
|
|
46
|
+
with set_user_id(request.user_id):
|
|
47
|
+
response = client.messages.create(
|
|
48
|
+
model="claude-sonnet-4-20250514",
|
|
49
|
+
max_tokens=1024,
|
|
50
|
+
messages=[{"role": "user", "content": request.message}],
|
|
51
|
+
)
|
|
52
|
+
reply = "".join(block.text for block in response.content if block.type == "text")
|
|
53
|
+
return {"reply": reply}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## What changed, and what did not
|
|
57
|
+
|
|
58
|
+
- `moda-ai` added to the project dependencies.
|
|
59
|
+
- One `moda.init` call added at module import time, before `Anthropic()` is
|
|
60
|
+
constructed.
|
|
61
|
+
- The handler wraps its existing Anthropic call in the scoped context
|
|
62
|
+
managers; the `messages.create` call itself is unchanged — instrumentation
|
|
63
|
+
is automatic.
|
|
64
|
+
- Shutdown flushes telemetry via the FastAPI lifespan hook.
|
|
65
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
66
|
+
runtime environment.
|