@moda-ai/cli 1.10.2 → 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/agent-schemas.js +60 -3
- package/dist/auth.js +5 -1
- package/dist/cli.js +124 -38
- package/dist/config.js +5 -3
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +40 -0
- package/dist/exit-funnel.d.ts +14 -0
- package/dist/exit-funnel.js +49 -0
- package/dist/harness.js +18 -6
- package/dist/init/index.js +57 -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/init/tenant.d.ts +6 -1
- package/dist/init/tenant.js +35 -11
- package/dist/manifest.d.ts +5 -0
- package/dist/manifest.js +111 -0
- package/dist/output.d.ts +44 -4
- package/dist/output.js +337 -32
- package/dist/prompts.d.ts +2 -1
- package/dist/prompts.js +38 -13
- package/dist/redact.d.ts +2 -0
- package/dist/redact.js +81 -0
- package/dist/schemas.d.ts +2 -2
- 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.d.ts +8 -2
- package/dist/skills.js +55 -161
- 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/skills/moda-cli/SKILL.md +51 -7
- package/dist/init/detect.d.ts +0 -9
- package/dist/init/detect.js +0 -179
|
@@ -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.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-python-anthropic
|
|
3
|
+
name: "Integrate Moda with the Anthropic SDK (Python)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: python
|
|
8
|
+
frameworks: []
|
|
9
|
+
providers:
|
|
10
|
+
- anthropic
|
|
11
|
+
sdk_package: moda-ai
|
|
12
|
+
summary: "Auto-instrument anthropic client calls with Moda in a Python app; no per-call changes required."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the Anthropic SDK (Python)
|
|
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.
|
|
22
|
+
|
|
23
|
+
## Safety rules (apply to every step)
|
|
24
|
+
|
|
25
|
+
- Only touch the repository you were launched in.
|
|
26
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
27
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
28
|
+
environment or a secret manager.
|
|
29
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
30
|
+
- Do not commit unless the user asks.
|
|
31
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
32
|
+
guessing.
|
|
33
|
+
|
|
34
|
+
## Step 1: Confirm the raw Anthropic client is the integration surface
|
|
35
|
+
|
|
36
|
+
Find the real LLM call sites: `client.messages.create(...)` or
|
|
37
|
+
`client.messages.stream(...)` on a client constructed with `Anthropic()`
|
|
38
|
+
from the `anthropic` package.
|
|
39
|
+
|
|
40
|
+
- If the Anthropic calls go through a wrapper framework instead (LangChain,
|
|
41
|
+
CrewAI, the Claude Agent SDK, or similar), stop: instrument the wrapper
|
|
42
|
+
layer with its dedicated skill, never the underlying provider client.
|
|
43
|
+
- If the repo wraps its Anthropic client in a helper module (for example
|
|
44
|
+
`app/llm.py` exporting a shared client), plan to instrument at that one
|
|
45
|
+
place; do not edit every call site.
|
|
46
|
+
- If there are no Anthropic call sites at all, stop and report.
|
|
47
|
+
|
|
48
|
+
## Step 2: Install the SDK
|
|
49
|
+
|
|
50
|
+
Install `moda-ai` with the package manager this repository already uses
|
|
51
|
+
(`uv.lock` means uv, `poetry.lock` means poetry, otherwise pip):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install moda-ai
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
With uv:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv add moda-ai
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
With poetry:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
poetry add moda-ai
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If `moda-ai` is already a dependency, skip the install. `moda-ai` includes
|
|
70
|
+
the OpenAI and Anthropic instrumentation packages; nothing else is needed
|
|
71
|
+
for this integration.
|
|
72
|
+
|
|
73
|
+
## Step 3: Initialize Moda once at startup, before the client is created
|
|
74
|
+
|
|
75
|
+
Instrumentation is automatic: once `moda.init` has run, every
|
|
76
|
+
`messages.create` / `messages.stream` call (including extended thinking and
|
|
77
|
+
tool use) is captured with no per-call changes.
|
|
78
|
+
|
|
79
|
+
`moda.init` is synchronous and must run before the Anthropic client is
|
|
80
|
+
constructed. If a `moda.init` call already exists, verify it runs at startup
|
|
81
|
+
before client creation and skip this step; never add a second call.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import os
|
|
85
|
+
|
|
86
|
+
import moda
|
|
87
|
+
from anthropic import Anthropic
|
|
88
|
+
|
|
89
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
90
|
+
|
|
91
|
+
client = Anthropic()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For web frameworks, initialize at import time of the application module (as
|
|
95
|
+
above) so the client-creation ordering holds; requirement: `moda.init` runs
|
|
96
|
+
before any `Anthropic()` construction.
|
|
97
|
+
|
|
98
|
+
## Step 4: Set conversation and user context
|
|
99
|
+
|
|
100
|
+
Set a stable conversation id before each LLM call so multi-turn
|
|
101
|
+
conversations group together, and a user id when one user can be identified.
|
|
102
|
+
|
|
103
|
+
For sequential code, use the module-level properties:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
moda.conversation_id = f"session_{session_id}"
|
|
107
|
+
moda.user_id = user_id
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For concurrent request handlers (any web server), use the scoped context
|
|
111
|
+
managers so overlapping requests do not leak context into each other:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from moda import set_conversation_id, set_user_id
|
|
115
|
+
|
|
116
|
+
with set_conversation_id(f"session_{session_id}"):
|
|
117
|
+
with set_user_id(user_id):
|
|
118
|
+
response = client.messages.create(
|
|
119
|
+
model="claude-sonnet-4-20250514",
|
|
120
|
+
max_tokens=1024,
|
|
121
|
+
messages=[{"role": "user", "content": user_message}],
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Reuse the session/thread identifier the app already has. If no session
|
|
126
|
+
identifier exists anywhere, the SDK falls back to hashing the first user
|
|
127
|
+
message plus the system prompt — acceptable only for simple chatbots that
|
|
128
|
+
pass full message history on every call. If this repo is an agent framework
|
|
129
|
+
or does not pass full history, an explicit conversation id is required; stop
|
|
130
|
+
and report if you cannot find a session identifier to use.
|
|
131
|
+
|
|
132
|
+
## Step 5: Flush before exit
|
|
133
|
+
|
|
134
|
+
Flush pending telemetry before the process exits (short-lived scripts) or on
|
|
135
|
+
shutdown (servers):
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
moda.flush()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Step 6: Validate
|
|
142
|
+
|
|
143
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
144
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
145
|
+
verify telemetry reaches Moda:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
moda doctor --online --json
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The report should show recent traces. If it does not, confirm `MODA_API_KEY`
|
|
152
|
+
is available in the runtime environment and that `moda.init()` runs before
|
|
153
|
+
the Anthropic client is created.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Golden reference: Claude Agent SDK + Moda
|
|
2
|
+
|
|
3
|
+
The complete finished integration for a minimal Claude Agent SDK script.
|
|
4
|
+
Match this structure; adapt names to the target repository.
|
|
5
|
+
|
|
6
|
+
## agent.py
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import asyncio
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import moda
|
|
13
|
+
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
|
|
14
|
+
|
|
15
|
+
# Initialize Moda before creating the ClaudeSDKClient. When
|
|
16
|
+
# moda-claude-agent-sdk is installed, the instrumentor is enabled
|
|
17
|
+
# automatically.
|
|
18
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
19
|
+
|
|
20
|
+
# Explicit conversation and user context so all turns group together.
|
|
21
|
+
moda.conversation_id = "session_123"
|
|
22
|
+
moda.user_id = "user_456"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def main():
|
|
26
|
+
options = ClaudeAgentOptions(model="claude-sonnet-4-20250514")
|
|
27
|
+
|
|
28
|
+
async with ClaudeSDKClient(options=options) as client:
|
|
29
|
+
await client.query("What is the capital of France?")
|
|
30
|
+
|
|
31
|
+
# Consume the full stream: token usage arrives in the final
|
|
32
|
+
# ResultMessage.
|
|
33
|
+
async for msg in client.receive_response():
|
|
34
|
+
print(msg)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
38
|
+
|
|
39
|
+
# Flush pending telemetry before exit.
|
|
40
|
+
moda.flush()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Dependencies
|
|
44
|
+
|
|
45
|
+
Both packages are required; `moda-claude-agent-sdk` is not included with
|
|
46
|
+
`moda-ai`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install moda-ai moda-claude-agent-sdk claude-agent-sdk
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## What changed, and what did not
|
|
53
|
+
|
|
54
|
+
- `moda-ai` and `moda-claude-agent-sdk` added to the project dependencies.
|
|
55
|
+
- One `moda.init` call added at startup, before `ClaudeSDKClient` is
|
|
56
|
+
constructed.
|
|
57
|
+
- Conversation and user ids set before the agent runs.
|
|
58
|
+
- The agent code itself is unchanged; the instrumentor wraps
|
|
59
|
+
`ClaudeSDKClient` automatically.
|
|
60
|
+
- No `.env` file was created or edited; `MODA_API_KEY` is read from the
|
|
61
|
+
runtime environment.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: integration-python-claude-agent-sdk
|
|
3
|
+
name: "Integrate Moda with the Claude Agent SDK (Python)"
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: integration
|
|
6
|
+
targets:
|
|
7
|
+
language: python
|
|
8
|
+
frameworks:
|
|
9
|
+
- claude-agent-sdk
|
|
10
|
+
providers: []
|
|
11
|
+
sdk_package: moda-claude-agent-sdk
|
|
12
|
+
summary: "Instrument Claude Agent SDK sessions (ClaudeSDKClient) with Moda via the dedicated moda-claude-agent-sdk instrumentor."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Integrate Moda with the Claude Agent SDK (Python)
|
|
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/claude-agent-sdk.
|
|
21
|
+
|
|
22
|
+
## Safety rules (apply to every step)
|
|
23
|
+
|
|
24
|
+
- Only touch the repository you were launched in.
|
|
25
|
+
- Never write the `MODA_API_KEY` value (or any secret) into source files, and
|
|
26
|
+
never create or edit `.env` files. The key belongs in the runtime
|
|
27
|
+
environment or a secret manager.
|
|
28
|
+
- Make the smallest correct change. Match the existing code style and imports.
|
|
29
|
+
- Do not commit unless the user asks.
|
|
30
|
+
- If a step is ambiguous in this repository, stop and report rather than
|
|
31
|
+
guessing.
|
|
32
|
+
|
|
33
|
+
## Why this integration needs a separate package
|
|
34
|
+
|
|
35
|
+
The Claude Agent SDK spawns Claude as a subprocess rather than making direct
|
|
36
|
+
Anthropic API calls, so standard Anthropic API instrumentation does not
|
|
37
|
+
fire. The `moda-claude-agent-sdk` package provides a dedicated instrumentor
|
|
38
|
+
that wraps `ClaudeSDKClient` to capture agent-level spans (token usage, tool
|
|
39
|
+
calls, turns, session metadata). The Claude Agent SDK is Python-only; if
|
|
40
|
+
this repository is Node.js, stop and report — there is no Node.js support.
|
|
41
|
+
|
|
42
|
+
## Step 1: Find the Claude Agent SDK call sites
|
|
43
|
+
|
|
44
|
+
Find where the application constructs `ClaudeSDKClient` (from
|
|
45
|
+
`claude_agent_sdk`). If there are none, stop and report: this skill only
|
|
46
|
+
applies to apps using the Claude Agent SDK.
|
|
47
|
+
|
|
48
|
+
## Step 2: Install the SDK packages
|
|
49
|
+
|
|
50
|
+
Install both `moda-ai` and `moda-claude-agent-sdk` with the package manager
|
|
51
|
+
this repository already uses (`uv.lock` means uv, `poetry.lock` means
|
|
52
|
+
poetry, otherwise pip). `moda-claude-agent-sdk` is NOT included with
|
|
53
|
+
`moda-ai`; it must be installed separately:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install moda-ai moda-claude-agent-sdk
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With uv:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv add moda-ai moda-claude-agent-sdk
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
With poetry:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
poetry add moda-ai moda-claude-agent-sdk
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If both are already dependencies, skip the install.
|
|
72
|
+
|
|
73
|
+
## Step 3: Initialize Moda once at startup, before the client is created
|
|
74
|
+
|
|
75
|
+
`moda.init` is synchronous, automatically enables the Claude Agent SDK
|
|
76
|
+
instrumentor when `moda-claude-agent-sdk` is installed, and must run before
|
|
77
|
+
any `ClaudeSDKClient` is constructed. If a `moda.init` call already exists,
|
|
78
|
+
verify it runs at startup and skip this step; never add a second call.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import os
|
|
82
|
+
|
|
83
|
+
import moda
|
|
84
|
+
|
|
85
|
+
moda.init(os.environ["MODA_API_KEY"])
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Step 4: Set conversation and user context
|
|
89
|
+
|
|
90
|
+
Set a stable conversation id (and user id when one user can be identified)
|
|
91
|
+
before running the agent, so all turns of a session group together:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
moda.conversation_id = f"session_{session_id}"
|
|
95
|
+
moda.user_id = user_id
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Reuse the session identifier the app already has. If no session identifier
|
|
99
|
+
exists anywhere, stop and report; do not invent one.
|
|
100
|
+
|
|
101
|
+
## Step 5: Consume the full response stream
|
|
102
|
+
|
|
103
|
+
Token usage comes from the `ResultMessage` at the end of the stream. Verify
|
|
104
|
+
the code consumes the full async generator from `receive_response()`; if it
|
|
105
|
+
breaks out early, token counts will be missing. Do not restructure working
|
|
106
|
+
code — just confirm the stream is fully consumed, and report if it is not.
|
|
107
|
+
|
|
108
|
+
## Step 6: Flush before exit
|
|
109
|
+
|
|
110
|
+
Flush pending telemetry before the process exits:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
moda.flush()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Step 7: Validate
|
|
117
|
+
|
|
118
|
+
Report the packages installed, files edited, and any step a human still needs
|
|
119
|
+
to do (for example setting `MODA_API_KEY` in the deploy environment). Then
|
|
120
|
+
verify telemetry reaches Moda:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
moda doctor --online --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The report should show recent traces. If it does not: confirm
|
|
127
|
+
`moda-claude-agent-sdk` is installed (it is separate from `moda-ai`), that
|
|
128
|
+
`moda.init()` runs before `ClaudeSDKClient` is created, and that
|
|
129
|
+
`MODA_API_KEY` is available in the runtime environment.
|