@respan/cli 0.11.0 → 0.13.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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Shared types for the `respan demo` orchestrator and its per-service seeders.
3
+ *
4
+ * The orchestrator (`commands/demo/*`) is service-agnostic: it knows only the
5
+ * {@link Seeder} contract and aggregates {@link SeederResult}s. Each service
6
+ * (prompts today; traces/datasets/evaluators later) ships its own `seed-*.ts`
7
+ * that implements {@link Seeder}, so adding a service is a drop-in.
8
+ */
9
+ import { RespanClient } from '@respan/respan-api';
10
+ /** Authed handles every seeder needs to talk to the platform. */
11
+ export interface SeederContext {
12
+ client: RespanClient;
13
+ /** `"Bearer <token>"`, as the SDK request objects expect. */
14
+ authHeader: string;
15
+ /**
16
+ * Resolved API base URL (e.g. `https://api.respan.ai`). Needed by seeders that
17
+ * bypass the SDK — the traces seeder POSTs raw OTLP to `/v2/traces`, which the
18
+ * SDK does not expose. Prompt seeding ignores this.
19
+ */
20
+ baseUrl: string;
21
+ }
22
+ /** A demo resource that already exists in the account (matched by name). */
23
+ export interface ExistingResource {
24
+ name: string;
25
+ id: string;
26
+ }
27
+ /** Outcome for a single resource. */
28
+ export interface SeedItemResult {
29
+ name: string;
30
+ status: 'created' | 'skipped' | 'deleted';
31
+ /** Resource ID — the new ID when created, the removed ID when deleted. */
32
+ id?: string;
33
+ /** Why it was skipped (e.g. "already exists" / "not found"). */
34
+ reason?: string;
35
+ }
36
+ /** Aggregated outcome for one service's seed or clear run. */
37
+ export interface SeederResult {
38
+ /** Machine key, e.g. `"prompts"`. */
39
+ service: string;
40
+ /** Human label for summaries, e.g. `"Prompts"`. */
41
+ label: string;
42
+ items: SeedItemResult[];
43
+ }
44
+ /** A per-service seeder. Owns its fixtures and all create/find/delete logic for
45
+ * one service, so the orchestrator stays service-agnostic. Both `run` and
46
+ * `clear` must be safely re-runnable (idempotent). */
47
+ export interface Seeder {
48
+ service: string;
49
+ label: string;
50
+ /** Create any missing demo resources, deploying them live. Skips existing. */
51
+ run(ctx: SeederContext): Promise<SeederResult>;
52
+ /** Delete the demo resources this seeder owns (matched by fixture name). */
53
+ clear(ctx: SeederContext): Promise<SeederResult>;
54
+ /** Find demo resources that currently exist in the account (by fixture name),
55
+ * without modifying anything. Used for dedup and for clear previews. */
56
+ findExisting(ctx: SeederContext): Promise<ExistingResource[]>;
57
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared types for the `respan demo` orchestrator and its per-service seeders.
3
+ *
4
+ * The orchestrator (`commands/demo/*`) is service-agnostic: it knows only the
5
+ * {@link Seeder} contract and aggregates {@link SeederResult}s. Each service
6
+ * (prompts today; traces/datasets/evaluators later) ships its own `seed-*.ts`
7
+ * that implements {@link Seeder}, so adding a service is a drop-in.
8
+ */
9
+ export {};
@@ -26,22 +26,55 @@ export declare abstract class SetupBaseCommand extends BaseCommand {
26
26
  /**
27
27
  * Shared setup flow for all three entry points.
28
28
  *
29
- * 1. askApiKey() // .env → global credential → prompt
30
- * 2. if mode is undefined → ask "Tracing or Gateway?" (interactive only)
31
- * 3. verifyApiKey(mode) // both re-prompt on a rejected key; can pause setup
32
- * 4. selectAgent() // drives only the launch
33
- * 5. installSkill() // full bundle, for all agents, regardless of selection
34
- * 6. launchAgent(mode) // only if an agent was selected
29
+ * 1. askEndpoint() // saved endpoint (confirm reuse) → environment prompt
30
+ * 2. askApiKey() // .env → global credential → prompt
31
+ * 3. if mode is undefined → ask "Tracing or Gateway?" (interactive only)
32
+ * 4. verifyApiKey(mode) // both re-prompt on a rejected key; can pause setup
33
+ * 5. selectAgent() // drives only the launch
34
+ * 6. installSkill() // full bundle, for all agents, regardless of selection
35
+ * 7. launchAgent(mode) // only if an agent was selected
35
36
  */
36
37
  protected runSetup(mode?: SetupMode, opts?: RunSetupOptions): Promise<void>;
38
+ /**
39
+ * Resolve which Respan endpoint this project should target, mirroring the
40
+ * environment prompt in `respan auth login`.
41
+ *
42
+ * First run: ask "Respan Platform or Enterprise?" and remember the choice.
43
+ * Later runs: offer the saved endpoint and confirm before reusing it, so a
44
+ * user who switched environments isn't silently pinned to the old one. An
45
+ * explicit `--base-url` flag wins and skips the prompt for scripted flows.
46
+ */
47
+ protected askEndpoint(projectRoot: string): Promise<string>;
48
+ /**
49
+ * Persist the chosen endpoint onto the existing saved credential — the same
50
+ * `~/.respan/credentials.json` store `auth login` uses. Only updates a
51
+ * credential that already exists; a first-time key is saved (with this
52
+ * baseUrl) later in {@link askApiKey}, so we never create a second store.
53
+ */
54
+ private rememberEndpoint;
55
+ /** Friendly name for the known endpoints; falls back to the raw URL. */
56
+ protected endpointLabel(baseUrl: string): string;
57
+ /** Dashboard origin that pairs with an ingest endpoint. */
58
+ protected dashboardUrl(baseUrl: string): string;
59
+ /**
60
+ * Pin the SDK's RESPAN_BASE_URL in `.env` when the endpoint isn't the
61
+ * default. The SDK already targets the Respan Platform out of the box, so we
62
+ * leave `.env` untouched for that choice and only write the override that a
63
+ * non-default endpoint actually needs (with the `/api` suffix the SDK wants).
64
+ */
65
+ protected persistBaseUrlToEnv(projectRoot: string, baseUrl: string): void;
66
+ /** Resolve the OTLP traces ingest URL for a base URL (with/without /api). */
67
+ protected resolveTracesEndpoint(baseUrl: string): string;
68
+ /** Resolve the gateway chat-completions URL for a base URL (with/without /api). */
69
+ protected resolveCompletionsEndpoint(baseUrl: string): string;
37
70
  protected askMode(step: number): Promise<SetupMode>;
38
- protected askApiKey(projectRoot: string): Promise<string>;
71
+ protected askApiKey(projectRoot: string, baseUrl?: string): Promise<string>;
39
72
  /**
40
73
  * Verify the key for the chosen mode. Both paths re-prompt on a rejected key
41
74
  * and return false if the user abandons the check, so the caller can pause
42
75
  * setup instead of reporting success with a key that doesn't work.
43
76
  */
44
- protected verifyApiKey(apiKey: string, mode: SetupMode, projectRoot: string): Promise<boolean>;
77
+ protected verifyApiKey(apiKey: string, mode: SetupMode, projectRoot: string, baseUrl: string): Promise<boolean>;
45
78
  /** Prompt for a replacement key and persist it to the project .env. */
46
79
  private reenterApiKey;
47
80
  private verifyTracingKey;
@@ -5,8 +5,8 @@ import { execSync, spawnSync } from 'node:child_process';
5
5
  import { Flags } from '@oclif/core';
6
6
  import { input, select, confirm } from '@inquirer/prompts';
7
7
  import { BaseCommand } from './base-command.js';
8
- import { getCredential, setCredential } from './config.js';
9
- import { DEFAULT_BASE_URL } from './auth.js';
8
+ import { getCredential, setCredential, getActiveProfile } from './config.js';
9
+ import { DEFAULT_BASE_URL, ENTERPRISE_BASE_URL } from './auth.js';
10
10
  import { printBanner } from './banner.js';
11
11
  import { findProjectRoot, writeTextFile, readTextFile, ensureDir, extractEnvVar, } from './integrate.js';
12
12
  import { createSpinner } from './spinner.js';
@@ -28,28 +28,31 @@ export class SetupBaseCommand extends BaseCommand {
28
28
  /**
29
29
  * Shared setup flow for all three entry points.
30
30
  *
31
- * 1. askApiKey() // .env → global credential → prompt
32
- * 2. if mode is undefined → ask "Tracing or Gateway?" (interactive only)
33
- * 3. verifyApiKey(mode) // both re-prompt on a rejected key; can pause setup
34
- * 4. selectAgent() // drives only the launch
35
- * 5. installSkill() // full bundle, for all agents, regardless of selection
36
- * 6. launchAgent(mode) // only if an agent was selected
31
+ * 1. askEndpoint() // saved endpoint (confirm reuse) → environment prompt
32
+ * 2. askApiKey() // .env → global credential → prompt
33
+ * 3. if mode is undefined → ask "Tracing or Gateway?" (interactive only)
34
+ * 4. verifyApiKey(mode) // both re-prompt on a rejected key; can pause setup
35
+ * 5. selectAgent() // drives only the launch
36
+ * 6. installSkill() // full bundle, for all agents, regardless of selection
37
+ * 7. launchAgent(mode) // only if an agent was selected
37
38
  */
38
39
  async runSetup(mode, opts = {}) {
39
40
  await printBanner();
40
41
  const projectRoot = findProjectRoot();
41
42
  const home = os.homedir();
42
- // Step numbers are assigned sequentially. The mode question (Step 2) only
43
- // runs for bare `respan setup`, so the concrete `setup tracing` / `setup
44
- // gateway` commands renumber the later steps instead of leaving a gap.
43
+ // Step numbers are assigned sequentially. The mode question only runs for
44
+ // bare `respan setup`, so the concrete `setup tracing` / `setup gateway`
45
+ // commands renumber the later steps instead of leaving a gap.
45
46
  let step = 1;
47
+ this.logStep(step++, 'Endpoint');
48
+ const baseUrl = await this.askEndpoint(projectRoot);
46
49
  this.logStep(step++, 'API Key');
47
- const apiKey = await this.askApiKey(projectRoot);
50
+ const apiKey = await this.askApiKey(projectRoot, baseUrl);
48
51
  // Ask the mode only when a concrete command didn't already pin it.
49
52
  const resolvedMode = mode ?? (await this.askMode(step));
50
53
  if (mode === undefined)
51
54
  step++;
52
- const verified = await this.verifyApiKey(apiKey, resolvedMode, projectRoot);
55
+ const verified = await this.verifyApiKey(apiKey, resolvedMode, projectRoot, baseUrl);
53
56
  if (!verified) {
54
57
  const ready = resolvedMode === 'gateway' ? 'your gateway access is ready' : 'you have a valid API key';
55
58
  this.log('');
@@ -68,11 +71,12 @@ export class SetupBaseCommand extends BaseCommand {
68
71
  this.log('');
69
72
  this.log(` ${DIM}Your API key is saved in ${RESET}.env`);
70
73
  this.log(` ${DIM}Respan skill installed for all agents${RESET}`);
74
+ const dashboard = this.dashboardUrl(baseUrl);
71
75
  if (resolvedMode === 'gateway') {
72
- this.log(` ${DIM}View logs at ${RESET}https://platform.respan.ai`);
76
+ this.log(` ${DIM}View logs at ${RESET}${dashboard}`);
73
77
  }
74
78
  else {
75
- this.log(` ${DIM}View traces at ${RESET}https://platform.respan.ai`);
79
+ this.log(` ${DIM}View traces at ${RESET}${dashboard}`);
76
80
  }
77
81
  this.log('');
78
82
  this.notifySetup(this.getGitEmail()).catch(() => { });
@@ -84,6 +88,107 @@ export class SetupBaseCommand extends BaseCommand {
84
88
  this.log(` ${DIM}No agent selected, but the skill is installed. Open your agent any time and use the ${RESET}/respan${DIM} skill.${RESET}`);
85
89
  }
86
90
  }
91
+ // ── Step 1: Endpoint ─────────────────────────────────────────────────
92
+ /**
93
+ * Resolve which Respan endpoint this project should target, mirroring the
94
+ * environment prompt in `respan auth login`.
95
+ *
96
+ * First run: ask "Respan Platform or Enterprise?" and remember the choice.
97
+ * Later runs: offer the saved endpoint and confirm before reusing it, so a
98
+ * user who switched environments isn't silently pinned to the old one. An
99
+ * explicit `--base-url` flag wins and skips the prompt for scripted flows.
100
+ */
101
+ async askEndpoint(projectRoot) {
102
+ // Explicit flag wins — CI/scripted parity with `auth login --base-url`.
103
+ const flagBaseUrl = this.globalFlags['base-url'];
104
+ if (flagBaseUrl) {
105
+ // `--base-url` is documented as a per-command override, so honor it for
106
+ // this run (verification + project .env) but don't rewrite the saved
107
+ // credential — that would silently redirect every other CLI command too.
108
+ const normalized = flagBaseUrl.replace(/\/+$/, '');
109
+ this.persistBaseUrlToEnv(projectRoot, normalized);
110
+ this.log(` ${GREEN}✓${RESET} Using endpoint ${DIM}${this.endpointLabel(normalized)}${RESET}`);
111
+ return normalized;
112
+ }
113
+ // Returning users: reuse the endpoint from their saved credential
114
+ // (the same store `auth login` writes) on confirmation.
115
+ const saved = getCredential()?.baseUrl;
116
+ if (saved) {
117
+ const reuse = await confirm({
118
+ message: `Use your saved endpoint (${this.endpointLabel(saved)})?`,
119
+ default: true,
120
+ });
121
+ if (reuse) {
122
+ this.persistBaseUrlToEnv(projectRoot, saved);
123
+ return saved;
124
+ }
125
+ }
126
+ // First time (or switching environments) — same choices as auth login.
127
+ const enterprise = await select({
128
+ message: 'Select your environment:',
129
+ choices: [
130
+ { name: 'Respan Platform', value: false },
131
+ { name: 'Enterprise', value: true },
132
+ ],
133
+ });
134
+ const baseUrl = enterprise ? ENTERPRISE_BASE_URL : DEFAULT_BASE_URL;
135
+ this.rememberEndpoint(baseUrl);
136
+ this.persistBaseUrlToEnv(projectRoot, baseUrl);
137
+ return baseUrl;
138
+ }
139
+ /**
140
+ * Persist the chosen endpoint onto the existing saved credential — the same
141
+ * `~/.respan/credentials.json` store `auth login` uses. Only updates a
142
+ * credential that already exists; a first-time key is saved (with this
143
+ * baseUrl) later in {@link askApiKey}, so we never create a second store.
144
+ */
145
+ rememberEndpoint(baseUrl) {
146
+ const profile = getActiveProfile();
147
+ const cred = getCredential(profile);
148
+ if (cred && cred.baseUrl !== baseUrl) {
149
+ setCredential(profile, { ...cred, baseUrl });
150
+ }
151
+ }
152
+ /** Friendly name for the known endpoints; falls back to the raw URL. */
153
+ endpointLabel(baseUrl) {
154
+ if (baseUrl === DEFAULT_BASE_URL)
155
+ return 'Respan Platform';
156
+ if (baseUrl === ENTERPRISE_BASE_URL)
157
+ return 'Enterprise';
158
+ return baseUrl;
159
+ }
160
+ /** Dashboard origin that pairs with an ingest endpoint. */
161
+ dashboardUrl(baseUrl) {
162
+ return baseUrl === ENTERPRISE_BASE_URL
163
+ ? 'https://enterprise.respan.ai'
164
+ : 'https://platform.respan.ai';
165
+ }
166
+ /**
167
+ * Pin the SDK's RESPAN_BASE_URL in `.env` when the endpoint isn't the
168
+ * default. The SDK already targets the Respan Platform out of the box, so we
169
+ * leave `.env` untouched for that choice and only write the override that a
170
+ * non-default endpoint actually needs (with the `/api` suffix the SDK wants).
171
+ */
172
+ persistBaseUrlToEnv(projectRoot, baseUrl) {
173
+ if (baseUrl === DEFAULT_BASE_URL)
174
+ return;
175
+ const normalized = baseUrl.replace(/\/+$/, '');
176
+ const sdkBaseUrl = normalized.endsWith('/api') ? normalized : `${normalized}/api`;
177
+ const envPath = path.join(projectRoot, '.env');
178
+ const existingEnv = readTextFile(envPath);
179
+ this.saveToEnv(envPath, existingEnv, 'RESPAN_BASE_URL', sdkBaseUrl);
180
+ this.log(` ${GREEN}✓${RESET} Saved endpoint to ${DIM}.env (RESPAN_BASE_URL)${RESET}`);
181
+ }
182
+ /** Resolve the OTLP traces ingest URL for a base URL (with/without /api). */
183
+ resolveTracesEndpoint(baseUrl) {
184
+ const normalized = baseUrl.replace(/\/+$/, '');
185
+ return normalized.endsWith('/api') ? `${normalized}/v2/traces` : `${normalized}/api/v2/traces`;
186
+ }
187
+ /** Resolve the gateway chat-completions URL for a base URL (with/without /api). */
188
+ resolveCompletionsEndpoint(baseUrl) {
189
+ const normalized = baseUrl.replace(/\/+$/, '');
190
+ return normalized.endsWith('/api') ? `${normalized}/chat/completions` : `${normalized}/api/chat/completions`;
191
+ }
87
192
  async askMode(step) {
88
193
  this.logStep(step, 'What to set up');
89
194
  return select({
@@ -100,8 +205,8 @@ export class SetupBaseCommand extends BaseCommand {
100
205
  ],
101
206
  });
102
207
  }
103
- // ── Step 1: API Key ──────────────────────────────────────────────────
104
- async askApiKey(projectRoot) {
208
+ // ── Step 2: API Key ──────────────────────────────────────────────────
209
+ async askApiKey(projectRoot, baseUrl = DEFAULT_BASE_URL) {
105
210
  const envPath = path.join(projectRoot, '.env');
106
211
  const existingEnv = readTextFile(envPath);
107
212
  const existingKey = extractEnvVar(existingEnv, 'RESPAN_API_KEY');
@@ -125,9 +230,9 @@ export class SetupBaseCommand extends BaseCommand {
125
230
  return globalCred.apiKey;
126
231
  }
127
232
  }
128
- // 3. No usable key — collect one from the platform.
233
+ // 3. No usable key — collect one from the chosen endpoint's dashboard.
129
234
  this.log('');
130
- this.log(` ${DIM}Get your API key at ${RESET}https://platform.respan.ai/settings/api-keys`);
235
+ this.log(` ${DIM}Get your API key at ${RESET}${this.dashboardUrl(baseUrl)}/settings/api-keys`);
131
236
  this.log('');
132
237
  const entered = await input({
133
238
  message: 'Enter your Respan API key:',
@@ -142,7 +247,7 @@ export class SetupBaseCommand extends BaseCommand {
142
247
  default: true,
143
248
  });
144
249
  if (saveGlobal) {
145
- setCredential('default', { type: 'api_key', apiKey, baseUrl: DEFAULT_BASE_URL });
250
+ setCredential('default', { type: 'api_key', apiKey, baseUrl });
146
251
  this.log(` ${GREEN}✓${RESET} Saved globally ${DIM}(~/.respan/credentials.json)${RESET}`);
147
252
  }
148
253
  return apiKey;
@@ -152,10 +257,10 @@ export class SetupBaseCommand extends BaseCommand {
152
257
  * and return false if the user abandons the check, so the caller can pause
153
258
  * setup instead of reporting success with a key that doesn't work.
154
259
  */
155
- async verifyApiKey(apiKey, mode, projectRoot) {
260
+ async verifyApiKey(apiKey, mode, projectRoot, baseUrl) {
156
261
  return mode === 'gateway'
157
- ? this.verifyGatewayKey(apiKey, projectRoot)
158
- : this.verifyTracingKey(apiKey, projectRoot);
262
+ ? this.verifyGatewayKey(apiKey, projectRoot, baseUrl)
263
+ : this.verifyTracingKey(apiKey, projectRoot, baseUrl);
159
264
  }
160
265
  /** Prompt for a replacement key and persist it to the project .env. */
161
266
  async reenterApiKey(envPath) {
@@ -168,8 +273,10 @@ export class SetupBaseCommand extends BaseCommand {
168
273
  this.saveToEnv(envPath, existingEnv, 'RESPAN_API_KEY', key);
169
274
  return key;
170
275
  }
171
- async verifyTracingKey(apiKey, projectRoot) {
276
+ async verifyTracingKey(apiKey, projectRoot, baseUrl) {
172
277
  const envPath = path.join(projectRoot, '.env');
278
+ const tracesUrl = this.resolveTracesEndpoint(baseUrl);
279
+ const dashboard = this.dashboardUrl(baseUrl);
173
280
  let key = apiKey;
174
281
  // eslint-disable-next-line no-constant-condition
175
282
  while (true) {
@@ -177,7 +284,7 @@ export class SetupBaseCommand extends BaseCommand {
177
284
  spinner.start();
178
285
  let status = 0;
179
286
  try {
180
- const response = await fetch('https://api.respan.ai/api/v2/traces', {
287
+ const response = await fetch(tracesUrl, {
181
288
  method: 'POST',
182
289
  headers: {
183
290
  'Authorization': `Bearer ${key}`,
@@ -190,14 +297,14 @@ export class SetupBaseCommand extends BaseCommand {
190
297
  catch {
191
298
  // Network/transient failure — tracing is lenient, so warn and continue.
192
299
  spinner.fail('Could not verify API key (network error)');
193
- this.warn(' Setup will continue, please verify manually at https://platform.respan.ai');
300
+ this.warn(` Setup will continue, please verify manually at ${dashboard}`);
194
301
  return true;
195
302
  }
196
303
  if (status >= 200 && status < 300) {
197
304
  spinner.succeed('API key verified');
198
305
  this.log('');
199
306
  this.log(` ${PC}A demo trace has been sent to your account.${RESET}`);
200
- this.log(` ${DIM}View it at ${RESET}https://platform.respan.ai${DIM} to see what Respan traces look like.${RESET}`);
307
+ this.log(` ${DIM}View it at ${RESET}${dashboard}${DIM} to see what Respan traces look like.${RESET}`);
201
308
  this.log('');
202
309
  await confirm({ message: 'Ready to continue?', default: true });
203
310
  return true;
@@ -217,7 +324,7 @@ export class SetupBaseCommand extends BaseCommand {
217
324
  }
218
325
  // Any other status — don't block setup on an unknown server state.
219
326
  spinner.fail(`Verification failed (status: ${status})`);
220
- this.warn(' Setup will continue, please verify manually at https://platform.respan.ai');
327
+ this.warn(` Setup will continue, please verify manually at ${dashboard}`);
221
328
  return true;
222
329
  }
223
330
  }
@@ -227,8 +334,10 @@ export class SetupBaseCommand extends BaseCommand {
227
334
  * positive balance. A rejected key (401/403) re-prompts; any other failure
228
335
  * loops on the user's say-so. Returns true on success, false if abandoned.
229
336
  */
230
- async verifyGatewayKey(apiKey, projectRoot) {
337
+ async verifyGatewayKey(apiKey, projectRoot, baseUrl) {
231
338
  const envPath = path.join(projectRoot, '.env');
339
+ const completionsUrl = this.resolveCompletionsEndpoint(baseUrl);
340
+ const dashboard = this.dashboardUrl(baseUrl);
232
341
  let key = apiKey;
233
342
  // eslint-disable-next-line no-constant-condition
234
343
  while (true) {
@@ -237,7 +346,7 @@ export class SetupBaseCommand extends BaseCommand {
237
346
  let status = 0;
238
347
  let networkError = false;
239
348
  try {
240
- const response = await fetch('https://api.respan.ai/api/chat/completions', {
349
+ const response = await fetch(completionsUrl, {
241
350
  method: 'POST',
242
351
  headers: {
243
352
  'Authorization': `Bearer ${key}`,
@@ -250,7 +359,7 @@ export class SetupBaseCommand extends BaseCommand {
250
359
  spinner.succeed('Gateway access verified');
251
360
  this.log('');
252
361
  this.log(` ${PC}A demo request was routed through the gateway.${RESET}`);
253
- this.log(` ${DIM}View it at ${RESET}https://platform.respan.ai${DIM} or run ${RESET}respan logs list --limit 5`);
362
+ this.log(` ${DIM}View it at ${RESET}${dashboard}${DIM} or run ${RESET}respan logs list --limit 5`);
254
363
  this.log('');
255
364
  await confirm({ message: 'Ready to continue?', default: true });
256
365
  return true;
@@ -1,5 +1,5 @@
1
1
  export declare const SKILL_MD = "# Respan\n\nUse the Respan CLI and SDK for LLM observability \u2014 tracing, evals, prompts, datasets, and gateway routing.\n\n## When To Use\n\n- **Set up tracing** (instrument your app to capture LLM calls \u2014 install + decorators, propagation, processors) \u2192 read [references/tracing.md](references/tracing.md)\n- **Set up gateway** (route LLM calls through the Respan proxy \u2014 wiring + model switching, fallbacks, caching) \u2192 read [references/gateway.md](references/gateway.md)\n- **Prompt management** (create, version, deploy) \u2192 read [references/prompts.md](references/prompts.md)\n- **Evals** (datasets, evaluators, experiments) \u2192 read [references/evals.md](references/evals.md)\n- **Monitors & automation** (alerts, online evals, webhooks) \u2192 read [references/monitors.md](references/monitors.md)\n\nTracing and gateway are separate setups \u2014 configure one at a time, never both in the same pass.\n\n## Core Principles\n\n1. **Read the reference first.** Each reference file has the exact setup steps, API patterns, MCP tools, and CLI commands.\n2. **Use MCP tools** for platform operations (prompts, datasets, evaluators, experiments, traces, logs).\n3. **Use CLI** when MCP is not available: `respan traces list`, `respan prompts list`, etc.\n4. **Fetch docs** for integration-specific details not covered in references.\n\n## Quick Reference\n\n| Task | Reference / Command |\n|------|--------------------|\n| Set up tracing (steps + decorators, propagation) | [references/tracing.md](references/tracing.md) |\n| Set up gateway (steps + features) | [references/gateway.md](references/gateway.md) |\n| Prompt management | [references/prompts.md](references/prompts.md) |\n| Evals & experiments | [references/evals.md](references/evals.md) |\n| Monitors & automation | [references/monitors.md](references/monitors.md) |\n| List traces | `respan traces list --limit 10` |\n| View a trace | `respan traces get <id>` |\n| Check auth | `respan auth status` |\n\n## Documentation Access\n\nAny doc page can be fetched as markdown:\n`https://respan.ai/docs/integrations/openai-sdk.md`\n`https://respan.ai/docs/sdks/typescript-sdk/overview.md`\n\nFull docs index: `https://www.respan.ai/docs/llms.txt`\n\nPlatform: `https://platform.respan.ai`\n";
2
- export declare const TRACING_MD = "# Tracing\n\nSet up and configure Respan SDK tracing: instrument an app to capture LLM calls as\nstructured traces, then refine with decorators, context propagation, and processors.\n\nThis file covers **both** the initial setup steps and the advanced reference. Tracing\nis a separate setup from the gateway \u2014 do one at a time, never both in the same pass.\n\nDocs: `https://respan.ai/docs/documentation/features/tracing/traces/tracing-sdk.md`\nFull docs index: `https://www.respan.ai/docs/llms.txt`\n\n---\n\n## Setup\n\nUse this section when the user asks to set up Respan tracing in their project.\n\n### Hard Rules\n\n- **Interactive mode:** Ask the user questions when you need input. Do not assume.\n- **Only add Respan code.** Do not refactor or modify unrelated code.\n- **Pin exact versions.** Never use `latest` or unpinned ranges.\n- **Do not guess APIs.** Use only the patterns from the integration docs linked below.\n- **If Respan is already installed/configured, do not duplicate work.** Check for existing `respan` imports first.\n- **Read the code before proposing changes.** Understand the actual workflow, not just the dependencies.\n\nThe API key is stored in `.env` as `RESPAN_API_KEY`.\n\n### 1. Analyze the Project\n\n**1a. Detect language and package manager:**\n- Check `package.json` (JS/TS) or `pyproject.toml` / `requirements.txt` (Python)\n- Detect package manager from lock files\n\n**1b. Detect libraries in priority order:**\n\nCheck higher-priority categories first. If a match is found, use that instrumentation \u2014 do NOT also add lower-level SDK instrumentation.\n\n**Priority 1 \u2014 Agent Frameworks & High-Level SDKs:**\n\n| Library | Python package | JS/TS package | Respan instrumentation (Python) | Respan instrumentation (JS/TS) | Docs |\n|---------|---------------|---------------|--------------------------------|-------------------------------|------|\n| Vercel AI SDK | \u2014 | `ai` | \u2014 | `@respan/instrumentation-vercel` | [docs](https://respan.ai/docs/integrations/vercel-ai-sdk.md) |\n| OpenAI Agents SDK | `openai-agents` | `@openai/agents` | `respan-instrumentation-openai-agents` | `@respan/instrumentation-openai-agents` | [docs](https://respan.ai/docs/integrations/openai-agents-sdk.md) |\n| Claude Agent SDK | `claude-agent-sdk` | \u2014 | `respan-instrumentation-claude-agent-sdk` | \u2014 | [docs](https://respan.ai/docs/integrations/claude-agents-sdk.md) |\n| Pydantic AI | `pydantic-ai` | \u2014 | `respan-instrumentation-pydantic-ai` | \u2014 | [docs](https://respan.ai/docs/integrations/pydantic-ai.md) |\n| LangChain | `langchain` | `langchain` | via OpenInference | \u2014 | [docs](https://respan.ai/docs/integrations/langchain.md) |\n| LangGraph | `langgraph` | \u2014 | via OpenInference | \u2014 | [docs](https://respan.ai/docs/integrations/langgraph.md) |\n| CrewAI | `crewai` | \u2014 | `respan-instrumentation-crewai` | \u2014 | [docs](https://respan.ai/docs/integrations/crewai.md) |\n| LlamaIndex | `llama-index` | \u2014 | via OpenInference | \u2014 | [docs](https://respan.ai/docs/integrations/llama-index.md) |\n| Haystack | `haystack-ai` | \u2014 | `respan-instrumentation-haystack` | \u2014 | [docs](https://respan.ai/docs/integrations/haystack.md) |\n| Mastra | \u2014 | `mastra` | \u2014 | via OTEL | [docs](https://respan.ai/docs/integrations/mastra.md) |\n| Google ADK | `google-adk` | \u2014 | via OpenInference | \u2014 | [docs](https://respan.ai/docs/integrations/google-adk.md) |\n\nIf a Priority 1 framework is found, use its instrumentation. Do NOT also add Priority 2 instrumentation for the same provider.\n\n**Priority 2 \u2014 Direct LLM SDKs** (only if no P1 framework covers this provider):\n\nThese are **auto-instrumented** \u2014 just `Respan()` / `new Respan()`, no extra packages needed:\n\n| Library | Python package | JS/TS package | Docs |\n|---------|---------------|---------------|------|\n| OpenAI SDK | `openai` | `openai` | [docs](https://respan.ai/docs/integrations/openai-sdk.md) |\n| Anthropic SDK | `anthropic` | `@anthropic-ai/sdk` | [docs](https://respan.ai/docs/integrations/anthropic.md) |\n| Azure OpenAI | `openai` (azure config) | `openai` | [docs](https://respan.ai/docs/integrations/providers/azure.md) |\n| Google Vertex AI | `google-cloud-aiplatform` | \u2014 | [docs](https://respan.ai/docs/integrations/vertex-ai.md) |\n| AWS Bedrock | `boto3` | \u2014 | [docs](https://respan.ai/docs/integrations/aws-bedrock.md) |\n| Cohere | `cohere` | \u2014 | [docs](https://respan.ai/docs/integrations/providers/cohere.md) |\n| Together AI | `together` | \u2014 | [docs](https://respan.ai/docs/integrations/together-ai.md) |\n\n**Note:** LiteLLM in JS uses the OpenAI-compatible API, so the OpenAI auto-instrument covers it. For Python LiteLLM, see [LiteLLM guide](https://respan.ai/docs/integrations/litellm.md). For Google GenAI (`@google/genai`), see [Google GenAI guide](https://respan.ai/docs/integrations/google-genai.md).\n\n**1c. Read the actual code and understand the workflow:**\n\nThis is the most important step. Read the entrypoint and all files that make LLM calls. Map out:\n\n- What is the **overall workflow**? (e.g. \"user sends question \u2192 retrieve context \u2192 generate answer \u2192 format response\")\n- What are the **individual steps/tasks**? (e.g. \"embed query\", \"search DB\", \"call GPT\", \"parse output\")\n- Are there **agent loops**? (e.g. a loop that calls tools until done)\n- Are there **tool calls**? (e.g. functions the LLM invokes)\n\n### 2. Propose an Implementation Plan\n\nPresent the user with a concrete plan before making any changes. The plan should include:\n\n**a) Packages to install** \u2014 core SDK + instrumentation package (with exact versions)\n\n**b) Initialization code** \u2014 where to add it (which file, which line)\n\n**c) Workflow structure** \u2014 how to wrap the existing code:\n\nFor **agent frameworks** (Priority 1): The framework instrumentation auto-captures the workflow structure. Usually just need init code, no manual wrapping needed. Fetch and follow the integration doc.\n\nFor **direct LLM SDKs** (Priority 2): Individual LLM calls will be auto-traced as flat spans. Propose wrapping the logical workflow with Respan decorators/wrappers to get structured nested traces:\n\nTypeScript example:\n```typescript\n// Before: flat traces \u2014 each LLM call is an isolated span\nconst outline = await openai.chat.completions.create({...});\nconst draft = await openai.chat.completions.create({...});\n\n// After: structured traces \u2014 nested spans showing the workflow\nconst result = await withWorkflow({ name: \"write_article\" }, async () => {\n const outline = await withTask({ name: \"generate_outline\" }, async () => {\n return await openai.chat.completions.create({...});\n });\n const draft = await withTask({ name: \"write_draft\" }, async () => {\n return await openai.chat.completions.create({...});\n });\n return draft;\n});\n```\n\nPython example:\n```python\n# Before: flat traces\noutline = client.chat.completions.create(...)\ndraft = client.chat.completions.create(...)\n\n# After: structured traces\n@workflow(name=\"write_article\")\ndef write_article(topic):\n outline = generate_outline(topic)\n return write_draft(outline)\n\n@task(name=\"generate_outline\")\ndef generate_outline(topic):\n return client.chat.completions.create(...)\n\n@task(name=\"write_draft\")\ndef write_draft(outline):\n return client.chat.completions.create(...)\n```\n\n**Ask the user which approach they prefer:**\n1. **Auto-trace only** \u2014 just add init code, every LLM call is automatically captured as a flat span. Zero code changes beyond initialization. Good for quick setup or simple projects.\n2. **Structured traces** \u2014 wrap existing code with workflow/task decorators for nested spans showing how the app flows. Better for complex projects with multiple LLM calls.\n\nIf the user picks option 1, skip the wrappers entirely \u2014 just install + init code.\n\nIf the user picks option 2:\n- **If multiple independent workflows are detected** (e.g. `writeArticle()`, `summarizeDoc()`, `classifyEmail()`), list them and ask which ones to instrument. Don't assume all of them.\n- **Show the user what the trace will look like** \u2014 describe the span hierarchy:\n```\nworkflow: write_article\n \u251C\u2500\u2500 task: generate_outline\n \u2502 \u2514\u2500\u2500 llm: openai.chat (auto-captured)\n \u2514\u2500\u2500 task: write_draft\n \u2514\u2500\u2500 llm: openai.chat (auto-captured)\n```\n\nWait for user confirmation before proceeding.\n\n### 3. Implement\n\n**a) Install packages:**\n\nFor direct LLM SDKs (Priority 2) \u2014 just the core SDK:\n```bash\n# Python\npip install respan-ai\n\n# TypeScript\nnpm install @respan/respan\n```\n\nFor agent frameworks (Priority 1) \u2014 also install the instrumentor. Check the docs link in the table above for the exact packages.\n\n**b) Add initialization code** \u2014 at the top of the entrypoint, before any LLM client is created:\n\nFor **direct LLM SDKs** (auto-instrumented):\n```python\n# Python\nfrom respan import Respan\nRespan()\n```\n```typescript\n// TypeScript\nimport { Respan } from \"@respan/respan\";\nconst respan = new Respan();\nawait respan.initialize();\n```\n\nFor **agent frameworks** (explicit instrumentor \u2014 fetch the docs URL from the table for the exact pattern):\n```python\n# Python example (OpenAI Agents)\nfrom respan import Respan\nfrom respan_instrumentation_openai_agents import OpenAIAgentsInstrumentor\nRespan(instrumentations=[OpenAIAgentsInstrumentor()])\n```\n```typescript\n// TypeScript example (OpenAI Agents)\nimport { Respan } from \"@respan/respan\";\nimport { OpenAIAgentsInstrumentor } from \"@respan/instrumentation-openai-agents\";\nconst respan = new Respan({ instrumentations: [new OpenAIAgentsInstrumentor()] });\nawait respan.initialize();\n```\n\n**c) Add workflow wrappers** \u2014 if the user chose structured traces in the plan.\n\n### 4. Verify (final test \u2014 always run this)\n\nAs the final step, **run the user's program once with a small request** and confirm tracing works end to end:\n\n- The app runs without errors\n- A trace appears at https://platform.respan.ai or via `respan traces list --limit 5`\n- If wrappers were added, the trace shows the expected nested span hierarchy\n\nIf no trace appears, the instrumentation isn't taking effect \u2014 debug (init placement, missing `flush()`, wrong entrypoint) and re-run before declaring setup done.\n\n---\n\n## Reference\n\nAdvanced tracing configuration: decorators, context propagation, processors, and span attributes.\n\n### Decorators\n\nWrap functions to create structured span hierarchies. All decorators share the same signature.\n\n| Decorator | Purpose |\n|-----------|---------|\n| `@workflow` / `withWorkflow` | Root span \u2014 top-level pipeline |\n| `@task` / `withTask` | Step within a workflow |\n| `@agent` / `withAgent` | Agent loop span |\n| `@tool` / `withTool` | Tool/function call span |\n\n#### Python\n\n```python\nfrom respan import Respan, workflow, task, agent, tool\n\nRespan()\n\n@workflow(name=\"write_article\")\ndef write_article(topic: str):\n outline = generate_outline(topic)\n return write_draft(outline)\n\n@task(name=\"generate_outline\")\ndef generate_outline(topic: str):\n return client.chat.completions.create(...)\n\n@task(name=\"write_draft\")\ndef write_draft(outline: str):\n return client.chat.completions.create(...)\n```\n\n#### TypeScript\n\n```typescript\nimport { Respan, withWorkflow, withTask } from \"@respan/respan\";\n\nconst respan = new Respan();\nawait respan.initialize();\n\nconst result = await withWorkflow({ name: \"write_article\" }, async () => {\n const outline = await withTask({ name: \"generate_outline\" }, async () => {\n return await client.chat.completions.create({...});\n });\n return await withTask({ name: \"write_draft\" }, async () => {\n return await client.chat.completions.create({...});\n });\n});\n```\n\n#### Decorator Parameters\n\n```python\n@workflow(\n name=\"my_workflow\", # Display name (default: function name)\n version=1, # Version number\n processors=\"my_processor\", # Route to specific processor(s)\n sample_rate=0.5, # 0.0-1.0, fraction of spans exported\n export_filter={ # Conditional export\n \"metadata.env\": {\"operator\": \"\", \"value\": \"production\"}\n },\n)\n```\n\nAll parameters are optional. Without any, the function name is used as the span name.\n\n#### Async Support\n\nDecorators work with `async def`, generators, and async generators automatically.\n\n### Context Propagation\n\nAttach attributes to **all spans** within a scope, including auto-instrumented LLM calls.\n\n#### propagate_attributes\n\n```python\nfrom respan import Respan, propagate_attributes\n\nRespan()\n\nwith propagate_attributes(\n customer_identifier=\"user_123\",\n thread_identifier=\"conv_abc\",\n environment=\"production\",\n metadata={\"plan\": \"pro\", \"team\": \"search\"},\n):\n # All spans here (including auto-instrumented OpenAI calls) get these attributes\n result = run_pipeline()\n```\n\n##### Available attributes\n\n| Attribute | Description |\n|-----------|-------------|\n| `customer_identifier` | User/customer ID \u2014 enables per-user analytics |\n| `customer_email` | Customer email |\n| `customer_name` | Customer display name |\n| `thread_identifier` | Conversation thread ID \u2014 groups related calls |\n| `custom_identifier` | Indexed custom identifier for fast queries |\n| `group_identifier` | Group related traces together |\n| `environment` | Environment name (e.g. `\"production\"`, `\"staging\"`) |\n| `metadata` | Dict of custom key-value pairs (merged in nested contexts) |\n| `prompt` | Dict with `prompt_id` and `variables` for prompt logging |\n\n##### TypeScript\n\n```typescript\nconst result = await respan.propagateAttributes(\n {\n customerIdentifier: \"user_123\",\n threadIdentifier: \"conv_abc\",\n metadata: { plan: \"pro\" },\n },\n async () => {\n return await runPipeline();\n }\n);\n```\n\n##### Nesting behavior\n\n- Nested `propagate_attributes` calls merge with the outer context\n- `metadata` dicts are merged (not replaced)\n- For duplicate keys, inner values override outer\n- Async-safe via `contextvars`\n\n#### respan_span_attributes\n\nAttaches attributes to the **current active span only** (not auto-instrumented child spans):\n\n```python\nfrom respan import respan_span_attributes\n\nwith respan_span_attributes({\n \"customer_identifier\": \"user-123\",\n \"metadata\": {\"priority\": \"high\"},\n}):\n pass\n```\n\nUse `propagate_attributes` when you need attributes on all nested spans (most common). Use `respan_span_attributes` when you only want to tag the current span.\n\n### Imperative Span Creation\n\nFor runtime-determined span names, use the client API instead of decorators:\n\n```python\nfrom respan import get_client\n\nclient = get_client()\n\nwith client.start_span(name=\"dynamic_step\", kind=\"task\") as span:\n result = do_work()\n # span is automatically closed and exported\n```\n\n### Updating the Current Span\n\nAdd attributes or change status on the active span at runtime:\n\n```python\nfrom respan import get_client\n\nclient = get_client()\n\n# Inside a decorated function or start_span context:\nclient.update_current_span(\n respan_params={\n \"customer_identifier\": \"user_123\",\n \"metadata\": {\"step\": \"final\"},\n },\n name=\"renamed_span\",\n)\n\n# Add events\nclient.add_event(\"checkpoint_reached\", attributes={\"items\": 42})\n\n# Record exceptions (span continues)\ntry:\n risky_operation()\nexcept Exception as e:\n client.record_exception(e)\n```\n\n### Custom Processors\n\nRoute spans to multiple destinations with filtering:\n\n```python\nfrom respan_tracing import RespanTelemetry\n\ntelemetry = RespanTelemetry(api_key=\"...\", base_url=\"...\")\n\n# Add a second exporter for debug logging\nfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter\n\ntelemetry.add_processor(\n exporter=ConsoleSpanExporter(),\n name=\"debug\",\n filter_fn=lambda span: span.attributes.get(\"environment\") == \"debug\",\n)\n```\n\nRoute specific spans to a processor using the `processors` parameter on decorators:\n\n```python\n@task(name=\"sensitive_step\", processors=\"debug\")\ndef sensitive_step():\n ...\n```\n\n### Flush\n\nAlways call `flush()` before process exit to ensure all spans are exported:\n\n```python\nrespan = Respan()\n# ... your code ...\nrespan.flush()\n```\n\nIn serverless/Lambda: call `flush()` at the end of every handler invocation.\n\n### Trace Hierarchy Example\n\n```\nworkflow: handle_request\n +-- task: classify_intent\n | +-- llm: openai.chat (auto-captured)\n +-- agent: support_agent\n | +-- tool: lookup_order\n | +-- llm: openai.chat (auto-captured)\n | +-- tool: process_refund\n +-- task: generate_response\n +-- llm: openai.chat (auto-captured)\n```\n\nLLM calls within decorated functions are automatically nested as child spans.\n";
2
+ export declare const TRACING_MD = "# Tracing\n\nSet up and configure Respan SDK tracing: instrument an app to capture LLM calls as\nstructured traces, then refine with decorators, context propagation, and processors.\n\nThis file covers **both** the initial setup steps and the advanced reference. Tracing\nis a separate setup from the gateway \u2014 do one at a time, never both in the same pass.\n\nDocs: `https://respan.ai/docs/documentation/features/tracing/traces/tracing-sdk.md`\nFull docs index: `https://www.respan.ai/docs/llms.txt`\n\n---\n\n## Setup\n\nUse this section when the user asks to set up Respan tracing in their project.\n\n### Hard Rules\n\n- **Interactive mode:** Ask the user questions when you need input. Do not assume.\n- **Only add Respan code.** Do not refactor or modify unrelated code.\n- **Pin exact versions.** Never use `latest` or unpinned ranges.\n- **Do not guess APIs.** Use only the patterns from the integration docs linked below.\n- **If Respan is already installed/configured, do not duplicate work.** Check for existing `respan` imports first.\n- **Read the code before proposing changes.** Understand the actual workflow, not just the dependencies.\n\nThe API key is stored in `.env` as `RESPAN_API_KEY`.\n\n### 1. Analyze the Project\n\n**1a. Detect language and package manager:**\n- Check `package.json` (JS/TS) or `pyproject.toml` / `requirements.txt` (Python)\n- Detect package manager from lock files\n\n**1b. Detect libraries in priority order:**\n\nCheck higher-priority categories first. If a match is found, use that instrumentation \u2014 do NOT also add lower-level SDK instrumentation.\n\n**Priority 1 \u2014 Agent Frameworks & High-Level SDKs:**\n\n| Library | Python package | JS/TS package | Respan instrumentation (Python) | Respan instrumentation (JS/TS) | Docs |\n|---------|---------------|---------------|--------------------------------|-------------------------------|------|\n| Vercel AI SDK | \u2014 | `ai` | \u2014 | `@respan/instrumentation-vercel` | [docs](https://respan.ai/docs/integrations/vercel-ai-sdk.md) |\n| OpenAI Agents SDK | `openai-agents` | `@openai/agents` | `respan-instrumentation-openai-agents` | `@respan/instrumentation-openai-agents` | [docs](https://respan.ai/docs/integrations/openai-agents-sdk.md) |\n| Claude Agent SDK | `claude-agent-sdk` | \u2014 | `respan-instrumentation-claude-agent-sdk` | \u2014 | [docs](https://respan.ai/docs/integrations/claude-agents-sdk.md) |\n| Pydantic AI | `pydantic-ai` | \u2014 | `respan-instrumentation-pydantic-ai` | \u2014 | [docs](https://respan.ai/docs/integrations/pydantic-ai.md) |\n| LangChain | `langchain` | `langchain` | `respan-instrumentation-langchain` (callback) | \u2014 | [docs](https://respan.ai/docs/integrations/langchain.md) |\n| LangGraph | `langgraph` | \u2014 | `respan-instrumentation-langchain` (callback) | \u2014 | [docs](https://respan.ai/docs/integrations/langgraph.md) |\n| CrewAI | `crewai` | \u2014 | `respan-instrumentation-crewai` | \u2014 | [docs](https://respan.ai/docs/integrations/crewai.md) |\n| LlamaIndex | `llama-index` | \u2014 | `respan-instrumentation-llama-index` | \u2014 | [docs](https://respan.ai/docs/integrations/llama-index.md) |\n| Haystack | `haystack-ai` | \u2014 | `respan-instrumentation-haystack` | \u2014 | [docs](https://respan.ai/docs/integrations/haystack.md) |\n| Mastra | \u2014 | `mastra` | \u2014 | via OTEL | [docs](https://respan.ai/docs/integrations/mastra.md) |\n| Google ADK | `google-adk` | \u2014 | `respan-instrumentation-google-adk` | \u2014 | [docs](https://respan.ai/docs/integrations/google-adk.md) |\n\nIf a Priority 1 framework is found, use its instrumentation. Do NOT also add Priority 2 instrumentation for the same provider.\n\n**LangChain / LangGraph use a callback pattern, not `Respan(instrumentations=[...])`.** Set up `RespanTelemetry` and pass `add_respan_callback(config=...)` into the chain/graph invocation: `from respan_tracing import RespanTelemetry` + `from respan_instrumentation_langchain import add_respan_callback`. (The other Priority-1 frameworks use the standard `Respan(instrumentations=[XInstrumentor()])` plugin pattern.)\n\n**Priority 2 \u2014 Direct LLM SDKs** (only if no P1 framework covers this provider):\n\nThese are **auto-instrumented** \u2014 just `Respan()` / `new Respan()`, no extra packages needed:\n\n| Library | Python package | JS/TS package | Docs |\n|---------|---------------|---------------|------|\n| OpenAI SDK | `openai` | `openai` | [docs](https://respan.ai/docs/integrations/openai-sdk.md) |\n| Anthropic SDK | `anthropic` | `@anthropic-ai/sdk` | [docs](https://respan.ai/docs/integrations/anthropic.md) |\n| Azure OpenAI | `openai` (azure config) | `openai` | [docs](https://respan.ai/docs/integrations/providers/azure.md) |\n| Google Vertex AI | `google-cloud-aiplatform` | \u2014 | [docs](https://respan.ai/docs/integrations/vertex-ai.md) |\n| AWS Bedrock | `boto3` | \u2014 | [docs](https://respan.ai/docs/integrations/aws-bedrock.md) |\n| Cohere | `cohere` | \u2014 | [docs](https://respan.ai/docs/integrations/providers/cohere.md) |\n| Together AI | `together` | \u2014 | [docs](https://respan.ai/docs/integrations/together-ai.md) |\n\n**Note:** LiteLLM in JS uses the OpenAI-compatible API, so the OpenAI auto-instrument covers it. For Python LiteLLM, see [LiteLLM guide](https://respan.ai/docs/integrations/litellm.md). For Google GenAI (`@google/genai`), see [Google GenAI guide](https://respan.ai/docs/integrations/google-genai.md).\n\n**1c. Read the actual code and understand the workflow:**\n\nThis is the most important step. Read the entrypoint and all files that make LLM calls. Map out:\n\n- What is the **overall workflow**? (e.g. \"user sends question \u2192 retrieve context \u2192 generate answer \u2192 format response\")\n- What are the **individual steps/tasks**? (e.g. \"embed query\", \"search DB\", \"call GPT\", \"parse output\")\n- Are there **agent loops**? (e.g. a loop that calls tools until done)\n- Are there **tool calls**? (e.g. functions the LLM invokes)\n\n### 2. Propose an Implementation Plan\n\nPresent the user with a concrete plan before making any changes. The plan should include:\n\n**a) Packages to install** \u2014 core SDK + instrumentation package (with exact versions)\n\n**b) Initialization code** \u2014 where to add it (which file, which line)\n\n**c) Workflow structure** \u2014 how to wrap the existing code:\n\nFor **agent frameworks** (Priority 1): The framework instrumentation auto-captures the workflow structure. Usually just need init code, no manual wrapping needed. Fetch and follow the integration doc.\n\nFor **direct LLM SDKs** (Priority 2): Individual LLM calls will be auto-traced as flat spans. Propose wrapping the logical workflow with Respan decorators/wrappers to get structured nested traces:\n\nTypeScript example:\n```typescript\n// Before: flat traces \u2014 each LLM call is an isolated span\nconst outline = await openai.chat.completions.create({...});\nconst draft = await openai.chat.completions.create({...});\n\n// After: structured traces \u2014 nested spans showing the workflow\nconst result = await withWorkflow({ name: \"write_article\" }, async () => {\n const outline = await withTask({ name: \"generate_outline\" }, async () => {\n return await openai.chat.completions.create({...});\n });\n const draft = await withTask({ name: \"write_draft\" }, async () => {\n return await openai.chat.completions.create({...});\n });\n return draft;\n});\n```\n\nPython example:\n```python\n# Before: flat traces\noutline = client.chat.completions.create(...)\ndraft = client.chat.completions.create(...)\n\n# After: structured traces\n@workflow(name=\"write_article\")\ndef write_article(topic):\n outline = generate_outline(topic)\n return write_draft(outline)\n\n@task(name=\"generate_outline\")\ndef generate_outline(topic):\n return client.chat.completions.create(...)\n\n@task(name=\"write_draft\")\ndef write_draft(outline):\n return client.chat.completions.create(...)\n```\n\n**Ask the user which approach they prefer:**\n1. **Auto-trace only** \u2014 just add init code, every LLM call is automatically captured as a flat span. Zero code changes beyond initialization. Good for quick setup or simple projects.\n2. **Structured traces** \u2014 wrap existing code with workflow/task decorators for nested spans showing how the app flows. Better for complex projects with multiple LLM calls.\n\nIf the user picks option 1, skip the wrappers entirely \u2014 just install + init code.\n\nIf the user picks option 2:\n- **If multiple independent workflows are detected** (e.g. `writeArticle()`, `summarizeDoc()`, `classifyEmail()`), list them and ask which ones to instrument. Don't assume all of them.\n- **Show the user what the trace will look like** \u2014 describe the span hierarchy:\n```\nworkflow: write_article\n \u251C\u2500\u2500 task: generate_outline\n \u2502 \u2514\u2500\u2500 llm: openai.chat (auto-captured)\n \u2514\u2500\u2500 task: write_draft\n \u2514\u2500\u2500 llm: openai.chat (auto-captured)\n```\n\nWait for user confirmation before proceeding.\n\n### 3. Implement\n\n**a) Install packages:**\n\nFor direct LLM SDKs (Priority 2) \u2014 just the core SDK:\n```bash\n# Python\npip install respan-ai\n\n# TypeScript\nnpm install @respan/respan\n```\n\nFor agent frameworks (Priority 1) \u2014 also install the instrumentor. Check the docs link in the table above for the exact packages.\n\n**b) Add initialization code** \u2014 at the top of the entrypoint, before any LLM client is created:\n\nFor **direct LLM SDKs** (auto-instrumented):\n```python\n# Python\nfrom respan import Respan\nRespan()\n```\n```typescript\n// TypeScript\nimport { Respan } from \"@respan/respan\";\nconst respan = new Respan();\nawait respan.initialize();\n```\n\nFor **agent frameworks** (explicit instrumentor \u2014 fetch the docs URL from the table for the exact pattern):\n```python\n# Python example (OpenAI Agents)\nfrom respan import Respan\nfrom respan_instrumentation_openai_agents import OpenAIAgentsInstrumentor\nRespan(instrumentations=[OpenAIAgentsInstrumentor()])\n```\n```typescript\n// TypeScript example (OpenAI Agents)\nimport { Respan } from \"@respan/respan\";\nimport { OpenAIAgentsInstrumentor } from \"@respan/instrumentation-openai-agents\";\nconst respan = new Respan({ instrumentations: [new OpenAIAgentsInstrumentor()] });\nawait respan.initialize();\n```\n\n**c) Add workflow wrappers** \u2014 if the user chose structured traces in the plan.\n\n### 4. Verify (final test \u2014 always run this)\n\nAs the final step, **run the user's program once with a small request** and confirm tracing works end to end:\n\n- The app runs without errors\n- A trace appears at https://platform.respan.ai or via `respan traces list --limit 5`\n- If wrappers were added, the trace shows the expected nested span hierarchy\n\nIf no trace appears, the instrumentation isn't taking effect \u2014 debug (init placement, missing `flush()`, wrong entrypoint) and re-run before declaring setup done.\n\n---\n\n## Reference\n\nAdvanced tracing configuration: decorators, context propagation, processors, and span attributes.\n\n### Decorators\n\nWrap functions to create structured span hierarchies. All decorators share the same signature.\n\n| Decorator | Purpose |\n|-----------|---------|\n| `@workflow` / `withWorkflow` | Root span \u2014 top-level pipeline |\n| `@task` / `withTask` | Step within a workflow |\n| `@agent` / `withAgent` | Agent loop span |\n| `@tool` / `withTool` | Tool/function call span |\n\n#### Python\n\n```python\nfrom respan import Respan, workflow, task, agent, tool\n\nRespan()\n\n@workflow(name=\"write_article\")\ndef write_article(topic: str):\n outline = generate_outline(topic)\n return write_draft(outline)\n\n@task(name=\"generate_outline\")\ndef generate_outline(topic: str):\n return client.chat.completions.create(...)\n\n@task(name=\"write_draft\")\ndef write_draft(outline: str):\n return client.chat.completions.create(...)\n```\n\n#### TypeScript\n\n```typescript\nimport { Respan, withWorkflow, withTask } from \"@respan/respan\";\n\nconst respan = new Respan();\nawait respan.initialize();\n\nconst result = await withWorkflow({ name: \"write_article\" }, async () => {\n const outline = await withTask({ name: \"generate_outline\" }, async () => {\n return await client.chat.completions.create({...});\n });\n return await withTask({ name: \"write_draft\" }, async () => {\n return await client.chat.completions.create({...});\n });\n});\n```\n\n#### Decorator Parameters\n\n```python\n@workflow(\n name=\"my_workflow\", # Display name (default: function name)\n version=1, # Version number\n processors=\"my_processor\", # Route to specific processor(s)\n sample_rate=0.5, # 0.0-1.0, fraction of spans exported\n export_filter={ # Conditional export\n \"metadata.env\": {\"operator\": \"\", \"value\": \"production\"}\n },\n)\n```\n\nAll parameters are optional. Without any, the function name is used as the span name.\n\n#### Async Support\n\nDecorators work with `async def`, generators, and async generators automatically.\n\n### Context Propagation\n\nAttach attributes to **all spans** within a scope, including auto-instrumented LLM calls.\n\n#### propagate_attributes\n\n```python\nfrom respan import Respan, propagate_attributes\n\nRespan()\n\nwith propagate_attributes(\n customer_identifier=\"user_123\",\n thread_identifier=\"conv_abc\",\n environment=\"production\",\n metadata={\"plan\": \"pro\", \"team\": \"search\"},\n):\n # All spans here (including auto-instrumented OpenAI calls) get these attributes\n result = run_pipeline()\n```\n\n##### Available attributes\n\n| Attribute | Description |\n|-----------|-------------|\n| `customer_identifier` | User/customer ID \u2014 enables per-user analytics |\n| `customer_email` | Customer email |\n| `customer_name` | Customer display name |\n| `thread_identifier` | Conversation thread ID \u2014 groups related calls |\n| `custom_identifier` | Indexed custom identifier for fast queries |\n| `group_identifier` | Group related traces together |\n| `environment` | Environment name (e.g. `\"production\"`, `\"staging\"`) |\n| `metadata` | Dict of custom key-value pairs (merged in nested contexts) |\n| `prompt` | Dict with `prompt_id` and `variables` for prompt logging |\n\n##### TypeScript\n\n```typescript\nconst result = await respan.propagateAttributes(\n {\n customerIdentifier: \"user_123\",\n threadIdentifier: \"conv_abc\",\n metadata: { plan: \"pro\" },\n },\n async () => {\n return await runPipeline();\n }\n);\n```\n\n##### Nesting behavior\n\n- Nested `propagate_attributes` calls merge with the outer context\n- `metadata` dicts are merged (not replaced)\n- For duplicate keys, inner values override outer\n- Async-safe via `contextvars`\n\n#### respan_span_attributes\n\nAttaches attributes to the **current active span only** (not auto-instrumented child spans):\n\n```python\nfrom respan import respan_span_attributes\n\nwith respan_span_attributes({\n \"customer_identifier\": \"user-123\",\n \"metadata\": {\"priority\": \"high\"},\n}):\n pass\n```\n\nUse `propagate_attributes` when you need attributes on all nested spans (most common). Use `respan_span_attributes` when you only want to tag the current span.\n\n### Imperative Span Creation\n\nFor runtime-determined span names, use the client API instead of decorators:\n\n```python\nfrom respan import get_client\n\nclient = get_client()\n\nwith client.start_span(name=\"dynamic_step\", kind=\"task\") as span:\n result = do_work()\n # span is automatically closed and exported\n```\n\n### Updating the Current Span\n\nAdd attributes or change status on the active span at runtime:\n\n```python\nfrom respan import get_client\n\nclient = get_client()\n\n# Inside a decorated function or start_span context:\nclient.update_current_span(\n respan_params={\n \"customer_identifier\": \"user_123\",\n \"metadata\": {\"step\": \"final\"},\n },\n name=\"renamed_span\",\n)\n\n# Add events\nclient.add_event(\"checkpoint_reached\", attributes={\"items\": 42})\n\n# Record exceptions (span continues)\ntry:\n risky_operation()\nexcept Exception as e:\n client.record_exception(e)\n```\n\n### Custom Processors\n\nRoute spans to multiple destinations with filtering:\n\n```python\nfrom respan_tracing import RespanTelemetry\n\ntelemetry = RespanTelemetry(api_key=\"...\", base_url=\"...\")\n\n# Add a second exporter for debug logging\nfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter\n\ntelemetry.add_processor(\n exporter=ConsoleSpanExporter(),\n name=\"debug\",\n filter_fn=lambda span: span.attributes.get(\"environment\") == \"debug\",\n)\n```\n\nRoute specific spans to a processor using the `processors` parameter on decorators:\n\n```python\n@task(name=\"sensitive_step\", processors=\"debug\")\ndef sensitive_step():\n ...\n```\n\n### Flush\n\nAlways call `flush()` before process exit to ensure all spans are exported:\n\n```python\nrespan = Respan()\n# ... your code ...\nrespan.flush()\n```\n\nIn serverless/Lambda: call `flush()` at the end of every handler invocation.\n\n### Trace Hierarchy Example\n\n```\nworkflow: handle_request\n +-- task: classify_intent\n | +-- llm: openai.chat (auto-captured)\n +-- agent: support_agent\n | +-- tool: lookup_order\n | +-- llm: openai.chat (auto-captured)\n | +-- tool: process_refund\n +-- task: generate_response\n +-- llm: openai.chat (auto-captured)\n```\n\nLLM calls within decorated functions are automatically nested as child spans.\n";
3
3
  export declare const GATEWAY_MD = "# Gateway\n\nSet up and use the Respan **gateway** \u2014 routing an app's LLM calls through the Respan\nproxy for logging, caching, key management, fallbacks, and model switching.\n\nThis file covers **both** the setup steps and the feature reference. Gateway is a\nseparate setup from tracing \u2014 do one at a time, never both in the same pass. Gateway\nsetup does **not** install instrumentation packages; it repoints the LLM client's base\nURL at the Respan proxy and applies the framework's specific wiring.\n\nDocs: `https://respan.ai/docs/documentation/features/gateway/gateway-quickstart.md`\nFull docs index: `https://www.respan.ai/docs/llms.txt`\n\n---\n\n## Setup\n\nUse this section when the user asks to set up the Respan gateway.\n\n### Hard Rules\n\n- **Interactive mode:** Ask the user questions when you need input. Do not assume.\n- **Only add Respan wiring.** Do not refactor or modify unrelated code.\n- **The gateway serves only the completion / text-generation operation**, exposed per provider:\n - OpenAI-compatible \u2192 `https://api.respan.ai/api/chat/completions`\n - Anthropic Messages \u2192 `https://api.respan.ai/api/anthropic/`\n - Google Gemini \u2192 `https://api.respan.ai/api/google/gemini/...`\n\n **Use the exact base path from the per-SDK gateway doc you fetch in Step 4** \u2014 it is authoritative. Do **NOT** repoint clients used for any other operation \u2014 embeddings, moderation, images, audio/speech, assistants, batch, fine-tuning \u2014 they are not supported and will fail (e.g. 404). Leave those untouched.\n- **Use chat completions, not the Responses API.** The gateway serves chat completions (`/api/chat/completions`), not the OpenAI Responses API (`/api/responses`). SDKs that *default* to the Responses API are still fully supported \u2014 you just switch them to chat-completions mode, per their per-SDK doc:\n - **OpenAI Agents SDK** \u2014 Python: `set_default_openai_client(AsyncOpenAI(base_url=..., api_key=os.environ[\"RESPAN_API_KEY\"]))` + `set_default_openai_api(\"chat_completions\")` (TS: `setDefaultOpenAIClient(...)` + `setOpenAIAPI(\"chat_completions\")`).\n - **Vercel AI SDK / `@ai-sdk/openai`** \u2014 use `provider.chat(model)`, not the default `provider(model)` (which targets the Responses API).\n- **Scope the wiring \u2014 do not blanket-rewrite.** Wire only the client(s) that make the completion call you are setting up. Do **NOT** repoint every client instance in the repo (especially in example/monorepo projects with many independent scripts).\n- **Do NOT install instrumentation packages.** Gateway routing does not use them. (That is tracing setup \u2014 see `tracing.md`.)\n- **Do not guess APIs or doc slugs.** Use only the patterns from the live integration doc you fetch, and the exact slug from the map below.\n- **Pin exact versions** if you install a framework SDK that is genuinely missing.\n- **If the gateway is already wired up, do not duplicate work.** Check for an existing `base_url`/`baseURL` pointing at `api.respan.ai` first.\n- **Read the code before proposing changes.** Find where the client is actually instantiated.\n\nThe API key is stored in `.env` as `RESPAN_API_KEY`. Gateway base URL: `https://api.respan.ai/api` (the proxy completions path is `https://api.respan.ai/api/chat/completions`).\n\n### 1. Detect\n\nIdentify:\n- **Language & package manager** \u2014 `package.json` (JS/TS), `pyproject.toml` / `requirements.txt` (Python), `Gemfile` (Ruby). Detect the package manager from lock files.\n- **LLM library / framework in use** \u2014 match the project's dependencies against the **Detection \u2192 slug map** below.\n\n### 2. Map to slug\n\nMatch the detected package(s) against the map. **Use the slug from the map verbatim \u2014 never auto-derive a slug from a display name** (the kebab-casing is irregular).\n\nApply the **priority rule:** a high-level framework wins over the raw SDK beneath it.\nFor example, a project with both `crewai` and `openai` maps to `crew-ai` (not `open-ai-sdk`),\nbecause their gateway wiring differs. **If two frameworks match, ask the user which to configure.**\n\n#### Detection \u2192 slug map\n\nURL pattern: `https://respan.ai/docs/integrations/gateway/<slug>.md`\n\n| Detected package(s) | Slug |\n|---|---|\n| `@openai/agents` / `openai-agents` | `open-ai-agents` |\n| `claude-agent-sdk` | `claude-agent-sdk` |\n| `ai` (Vercel AI SDK) | `vercel-ai-sdk` |\n| `pydantic-ai` | `pydantic-ai` |\n| `crewai` | `crew-ai` |\n| `haystack-ai` | `haystack` |\n| `langchain` | `lang-chain` |\n| `llama-index` | `llama-index` |\n| `autogen` / `autogen-agentchat` | `auto-gen` |\n| `dspy` / `dspy-ai` | `ds-py` |\n| `google-adk` | `google-adk` |\n| `openai` | `open-ai-sdk` |\n| `anthropic` / `@anthropic-ai/sdk` | `anthropic-sdk` |\n| `google-genai` / `@google/genai` | `google-gen-ai` |\n| `litellm` | `lite-llm` |\n| `ruby_llm` (gem) | `ruby-llm` |\n| `google-cloud-aiplatform` | `vertex-ai` |\n\n> Namespace note: OpenAI, Anthropic, and Vertex each have both an SDK-level gateway page (`open-ai-sdk`, `anthropic-sdk`, `vertex-ai`) and a `model-providers/*` page. The SDK-level slugs above are the correct targets for this map.\n\n### 3. Confirm\n\nShow the user what was detected and which doc will be used. Wait for confirmation.\n\n> Detected:\n> - Language: [TypeScript/Python/Ruby]\n> - Framework / SDK: [e.g. OpenAI Agents]\n> - Gateway doc: https://respan.ai/docs/integrations/gateway/open-ai-agents.md\n>\n> Set up the gateway for this? (yes/no)\n\n### 4. Fetch the live doc\n\nFetch `https://respan.ai/docs/integrations/gateway/<slug>.md` for the chosen slug.\nUse the slug from the map verbatim.\n\nIf a pinned URL fails to resolve, that signals the **map is stale** and the table should be\nupdated \u2014 it does **not** mean you should guess an alternative slug.\n\n### 5. Read the project's client code\n\nFind where the LLM client / `base_url` is instantiated, and identify **which client(s) make\ncompletion / text-generation calls** (chat completions, Anthropic `messages.create`, Gemini\n`generate_content`) \u2014 that is the only path you repoint. This is the step that determines the\ncorrect wiring \u2014 do not skip it.\n\nIf the project has multiple clients or many scripts (e.g. an examples repo), repoint **only**\nthe completion ones. Leave clients that call embeddings, moderation, images, audio,\nassistants, batch, or fine-tuning exactly as they are \u2014 the gateway does not serve them.\n\n### 6. Apply the framework-specific wiring\n\nApply the exact pattern from the fetched doc:\n\n- Ensure `RESPAN_API_KEY` is set (it is in `.env`).\n- Repoint `base_url` / `baseURL` to the gateway path from the fetched doc (e.g. `https://api.respan.ai/api` for OpenAI-compatible, `\u2026/api/anthropic/` for Anthropic) \u2014 **only on the completion client(s)** identified in step 5, not on every client in the project.\n- Apply the framework's exact wiring \u2014 e.g. OpenAI Agents requires `set_default_openai_client(AsyncOpenAI(base_url=...))`, **not** env vars.\n- Install the framework SDK only if it is genuinely missing.\n- **Do NOT install instrumentation packages.**\n\n### 7. Verify (final test \u2014 always run this)\n\nAs the final step, **run the user's program once with a small request** through the\nconfigured gateway and confirm it works:\n\n```bash\nrespan logs list --limit 5\n```\n\nConfirm:\n- The app runs without errors\n- The request appears in the gateway logs (via the CLI above, or the Respan platform at https://platform.respan.ai)\n\nIf no log entry appears, the request is not actually routing through the gateway \u2014 debug\nthe `base_url`/wiring and re-run before declaring setup done.\n\n---\n\n## Reference\n\n### Wiring Patterns\n\nPoint the LLM client's `base_url` at the Respan gateway. The Respan API key authenticates both the proxy and tracing.\n\n#### Python (OpenAI SDK)\n\n```python\nimport os\nfrom openai import OpenAI\n\nclient = OpenAI(\n api_key=os.environ[\"RESPAN_API_KEY\"],\n base_url=os.getenv(\"RESPAN_BASE_URL\", \"https://api.respan.ai/api\"),\n)\n\nresponse = client.chat.completions.create(\n model=\"gpt-4o-mini\",\n messages=[{\"role\": \"user\", \"content\": \"Hello!\"}],\n)\n```\n\n#### TypeScript (OpenAI SDK)\n\n```typescript\nimport OpenAI from \"openai\";\n\nconst client = new OpenAI({\n apiKey: process.env.RESPAN_API_KEY,\n baseURL: process.env.RESPAN_BASE_URL || \"https://api.respan.ai/api\",\n});\n\nconst response = await client.chat.completions.create({\n model: \"gpt-4o-mini\",\n messages: [{ role: \"user\", content: \"Hello!\" }],\n});\n```\n\n#### Python (Anthropic SDK)\n\n```python\nimport os\nimport anthropic\n\nclient = anthropic.Anthropic(\n api_key=os.environ[\"RESPAN_API_KEY\"],\n base_url=os.getenv(\"RESPAN_BASE_URL\", \"https://api.respan.ai/api\"),\n)\n```\n\n#### Environment variables\n\nSet once, works everywhere:\n\n```bash\nexport RESPAN_API_KEY=\"your-key\"\nexport OPENAI_API_KEY=\"$RESPAN_API_KEY\"\nexport OPENAI_BASE_URL=\"https://api.respan.ai/api\"\n```\n\n### Framework-Specific Gateway Setup\n\n#### CrewAI\n\n```python\nimport os\nos.environ[\"OPENAI_API_KEY\"] = os.environ[\"RESPAN_API_KEY\"]\nos.environ[\"OPENAI_BASE_URL\"] = os.getenv(\"RESPAN_BASE_URL\", \"https://api.respan.ai/api\")\n\nfrom crewai import Agent, Task, Crew\n# CrewAI uses the env vars automatically\n```\n\n#### Haystack\n\n```python\nimport os\nos.environ[\"OPENAI_API_KEY\"] = os.environ[\"RESPAN_API_KEY\"]\nos.environ[\"OPENAI_BASE_URL\"] = os.getenv(\"RESPAN_BASE_URL\", \"https://api.respan.ai/api\")\n\nfrom haystack.components.generators import OpenAIGenerator\n# OpenAIGenerator picks up env vars automatically\n```\n\n#### OpenAI Agents SDK\n\n```python\nimport os\nos.environ[\"OPENAI_API_KEY\"] = os.environ[\"RESPAN_API_KEY\"]\nos.environ[\"OPENAI_BASE_URL\"] = os.getenv(\"RESPAN_BASE_URL\", \"https://api.respan.ai/api\")\n```\n\n#### Pydantic AI\n\n```python\nimport os\nos.environ[\"OPENAI_API_KEY\"] = os.environ[\"RESPAN_API_KEY\"]\nos.environ[\"OPENAI_BASE_URL\"] = os.getenv(\"RESPAN_BASE_URL\", \"https://api.respan.ai/api\")\n\nfrom pydantic_ai import Agent\nagent = Agent(model=\"openai:gpt-4o-mini\")\n```\n\n### Model Switching\n\nThe gateway supports 250+ models across providers. Use any model name:\n\n```python\n# OpenAI models\nresponse = client.chat.completions.create(model=\"gpt-4o\", ...)\n\n# Anthropic models (via OpenAI SDK!)\nresponse = client.chat.completions.create(model=\"claude-sonnet-4-20250514\", ...)\n\n# Google models\nresponse = client.chat.completions.create(model=\"gemini-2.0-flash\", ...)\n\n# Open-source models\nresponse = client.chat.completions.create(model=\"llama-3.1-70b\", ...)\n```\n\nNo code changes needed \u2014 just change the model string. View all models at `https://platform.respan.ai/platform/models`.\n\n### Advanced Features\n\n#### Fallback Models\n\nAutomatic failover if the primary model fails:\n\n```python\nresponse = client.chat.completions.create(\n model=\"gpt-4o\",\n messages=[...],\n extra_body={\n \"fallback_models\": [\"claude-sonnet-4-20250514\", \"gemini-2.0-flash\"],\n },\n)\n```\n\n#### Retries\n\n```python\nresponse = client.chat.completions.create(\n model=\"gpt-4o\",\n messages=[...],\n extra_body={\n \"retries\": 3,\n },\n)\n```\n\n#### Caching\n\nEnable response caching to save cost on repeated calls:\n\n```python\nresponse = client.chat.completions.create(\n model=\"gpt-4o\",\n messages=[...],\n extra_body={\n \"cache_enabled\": True,\n \"cache_ttl\": 3600, # seconds\n },\n)\n```\n\n#### Streaming\n\nStreaming works normally through the gateway:\n\n```python\nstream = client.chat.completions.create(\n model=\"gpt-4o-mini\",\n messages=[{\"role\": \"user\", \"content\": \"Tell me a story\"}],\n stream=True,\n)\nfor chunk in stream:\n print(chunk.choices[0].delta.content, end=\"\")\n```\n\n#### Disable Logging\n\nFor sensitive data, disable logging on specific requests:\n\n```python\nresponse = client.chat.completions.create(\n model=\"gpt-4o\",\n messages=[...],\n extra_body={\n \"disable_log\": True,\n },\n)\n```\n\n### Key Benefits\n\n- **Unified key management** \u2014 one Respan API key for all providers\n- **Automatic logging** \u2014 every request logged as a span\n- **Cost tracking** \u2014 per-request cost tracking across providers\n- **Caching** \u2014 cache repeated LLM calls to reduce cost and latency\n- **Fallbacks** \u2014 automatic failover between providers\n- **Model switching** \u2014 swap models without code changes\n- **~50-150ms added latency** \u2014 minimal overhead\n";
4
4
  export declare const PROMPTS_MD = "# Prompts Reference\n\nCreate, version, deploy, and use prompt templates from the Respan platform.\n\nDocs: `https://respan.ai/docs/documentation/features/prompt-management/prompt-management-quickstart.md`\n\n---\n\n## Overview\n\nPrompts are version-controlled templates stored on the Respan platform. The workflow is:\n\n1. **Create** a prompt with a name\n2. **Add versions** with messages, model config, and `{{variables}}`\n3. **Deploy** the version you want live\n4. **Fetch** the prompt in code using the API or MCP tools\n5. **Iterate** \u2014 commit new versions and deploy without code changes\n\n---\n\n## Create a Prompt\n\n### Via MCP\n\n```\nUse create_prompt to create a prompt:\n name: \"classify_intent\"\n description: \"Classifies user messages into intent categories\"\n\nThen use create_prompt_version to add content:\n prompt_id: \"<id from above>\"\n model: \"gpt-4o-mini\"\n messages:\n - role: \"system\"\n content: \"You are an intent classifier. Classify the user message into one of: question, complaint, request, feedback.\"\n - role: \"user\"\n content: \"{{user_message}}\"\n```\n\n### Via CLI\n\n```bash\nrespan prompts create --name \"classify_intent\"\nrespan prompts versions <prompt-id>\n```\n\n---\n\n## Variables\n\nUse `{{variable_name}}` in message content. Variables are filled at runtime.\n\n```json\n{\n \"messages\": [\n {\"role\": \"system\", \"content\": \"You are a {{role}} assistant.\"},\n {\"role\": \"user\", \"content\": \"{{user_input}}\"}\n ]\n}\n```\n\n### Jinja templates\n\nPrompts support Jinja2 syntax:\n\n- **Conditionals:** `{% if context %}Use this context: {{context}}{% endif %}`\n- **JSON access:** `{{ input.key }}`\n- **Filters:** `{{ var | upper }}`\n- **Loops:** `{% for item in items %}{{item}}{% endfor %}`\n- **Default values:** Set via the API or platform UI\n\n---\n\n## Versioning and Deployment\n\nEach prompt can have multiple versions. Only one version is **deployed** (live) at a time.\n\n### Workflow\n\n1. `create_prompt_version` \u2014 creates a new version (always starts as NOT deployed)\n2. Test the version in the playground or via API\n3. Deploy via platform UI when ready\n4. Code using `prompt_id` automatically gets the deployed version\n\n### Version pinning\n\n```python\n# Always use the deployed version (default)\nprompt = get_prompt(prompt_id=\"abc123\")\n\n# Pin to a specific version\nprompt = get_prompt(prompt_id=\"abc123\", version=3)\n```\n\n---\n\n## Using Prompts in Code\n\n### Python (via Gateway)\n\nFetch the prompt config and pass to the LLM:\n\n```python\nimport os\nfrom openai import OpenAI\n\nclient = OpenAI(\n api_key=os.environ[\"RESPAN_API_KEY\"],\n base_url=os.getenv(\"RESPAN_BASE_URL\", \"https://api.respan.ai/api\"),\n)\n\n# The gateway resolves the prompt template and fills variables\nresponse = client.chat.completions.create(\n model=\"gpt-4o-mini\",\n messages=[], # Messages come from the prompt template\n extra_body={\n \"prompt_id\": \"your-prompt-id\",\n \"prompt_variables\": {\n \"user_message\": \"I want to return my order\",\n },\n },\n)\n```\n\n### Linking traces to prompts\n\nUse `propagate_attributes` to tag traces with the prompt used:\n\n```python\nfrom respan import propagate_attributes\n\nwith propagate_attributes(\n prompt={\"prompt_id\": \"abc123\", \"variables\": {\"user_message\": \"Hello\"}},\n):\n response = client.chat.completions.create(...)\n```\n\n---\n\n## Prompt Composition\n\nVariables can reference other prompts (child prompts rendered first, injected as text).\n\nConfigure in the platform UI by changing a variable's type from **Text** to **Prompt**.\n\nMax nesting depth: 2 (parent -> child).\n\n---\n\n## Structured Output (JSON Schema)\n\nAttach a JSON schema to a prompt version for structured responses:\n\n```json\n{\n \"response_format\": {\n \"type\": \"json_schema\",\n \"json_schema\": {\n \"name\": \"extraction\",\n \"strict\": true,\n \"schema\": {\n \"type\": \"object\",\n \"properties\": {\n \"intent\": {\"type\": \"string\"},\n \"confidence\": {\"type\": \"number\"}\n },\n \"required\": [\"intent\", \"confidence\"],\n \"additionalProperties\": false\n }\n }\n }\n}\n```\n\n---\n\n## MCP Tools Reference\n\n| Action | Tool |\n|--------|------|\n| List all prompts | `list_prompts` |\n| Get prompt details | `get_prompt_detail(prompt_id)` |\n| Create a prompt | `create_prompt(name, description)` |\n| Update name/description | `update_prompt(prompt_id, name, description)` |\n| Create a version | `create_prompt_version(prompt_id, messages, model, ...)` |\n| Update a version | `update_prompt_version(prompt_id, version, ...)` |\n| List versions | `list_prompt_versions(prompt_id)` |\n| Get version detail | `get_prompt_version_detail(prompt_id, version)` |\n\n---\n\n## CLI Commands\n\n```bash\nrespan prompts list # List all prompts\nrespan prompts get <id> # Get prompt details\nrespan prompts create --name <name> # Create a prompt\nrespan prompts update <id> # Update a prompt\nrespan prompts versions <id> # List prompt versions\n```\n";
5
5
  export declare const EVALS_MD = "# Evals Reference\n\nBuild evaluation pipelines: datasets, evaluators, and experiments.\n\nDocs: `https://respan.ai/docs/documentation/features/evals/concepts.md`\n\n---\n\n## Overview\n\nTwo evaluation modes:\n\n- **Offline** \u2014 build datasets, run experiments across prompt/model variants, compare scores\n- **Online** \u2014 automatically score live production traffic (see [monitors.md](monitors.md))\n\nOffline eval workflow:\n\n```\nCreate dataset -> Create evaluator -> Create experiment -> Run & compare\n```\n\n---\n\n## Datasets\n\nA dataset is a collection of test rows (input/output pairs) for experiments.\n\n### Create from scratch\n\n```\nUse create_dataset:\n name: \"intent_classification_testset\"\n description: \"Test cases for intent classifier\"\n\nThen add rows with create_dataset_span:\n dataset_id: \"<id>\"\n input: '{\"user_message\": \"I want a refund\"}'\n output: '{\"intent\": \"refund_request\"}'\n```\n\n### Create from production traces\n\nSample real spans from traced data:\n\n```\n1. Use list_traces or list_logs to find relevant spans\n2. Use add_spans_to_dataset to add span IDs to a dataset:\n dataset_id: \"<id>\"\n span_ids: [\"span_1\", \"span_2\", \"span_3\"]\n```\n\nOr use the platform UI: Datasets -> Insert by sampling (filter, set sampling %, preview, confirm).\n\n### CSV import\n\nUpload CSV via the platform UI. Columns map to prompt variables. Optional `ideal_output` column for expected outputs. Max 500 rows per import.\n\n### MCP tools\n\n| Action | Tool |\n|--------|------|\n| Create dataset | `create_dataset(name, description)` |\n| List datasets | `list_datasets` |\n| Get dataset | `get_dataset(dataset_id)` |\n| Update dataset | `update_dataset(dataset_id, name, description)` |\n| Add a row manually | `create_dataset_span(dataset_id, input, output)` |\n| Add existing spans | `add_spans_to_dataset(dataset_id, span_ids)` |\n| List rows | `list_dataset_spans(dataset_id)` |\n| Get a row | `retrieve_dataset_span(dataset_id, log_id)` |\n| Update a row | `update_dataset_span(dataset_id, log_id, ...)` |\n\n---\n\n## Evaluators\n\nAn evaluator scores LLM outputs. It is a workflow built from **graders** connected with conditions.\n\n### Grader types\n\n| Type | How it works |\n|------|-------------|\n| **LLM grader** | A language model judges the output. Uses variables: `{{output}}`, `{{input}}`, `{{expected_output}}`, `{{metadata}}`, `{{metrics}}` |\n| **Code grader** | A Python function returns a score. Signature: `main(eval_inputs)` where `eval_inputs[\"output\"]` is required |\n| **Human grader** | Team members review and score outputs manually |\n\n### Score value types\n\n| Type | Use case |\n|------|----------|\n| `numerical` | Rating scales (e.g. 0-5) |\n| `boolean` | Pass/fail checks |\n| `categorical` | Multi-choice classifications |\n| `comment` | Qualitative text feedback |\n\n### Create an evaluator\n\n```\nUse create_evaluator:\n name: \"response_quality\"\n score_value_type: \"numerical\"\n type: \"llm\"\n description: \"Scores response quality 1-5\"\n evaluator_slug: \"response-quality\"\n```\n\nFor complex evaluators with grader workflows, conditions, and compute blocks \u2014 use the platform UI (Evaluators -> Create).\n\n### Evaluator workflow blocks\n\n- **Markers:** `Original input`, `Final result` (entry/exit)\n- **Graders:** LLM, Code, or Human scoring\n- **Conditions:** `If...Then...Else` branching based on scores\n- **Compute:** Average/weighted average of multiple grader scores\n- **Metrics:** Built-in: completion tokens, cost, latency, model, prompt tokens, total tokens\n- **Constants:** Fixed threshold values\n\n### MCP tools\n\n| Action | Tool |\n|--------|------|\n| List evaluators | `list_evaluators` |\n| Get evaluator | `get_evaluator(evaluator_id)` |\n| Create evaluator | `create_evaluator(name, score_value_type, ...)` |\n| Update evaluator | `update_evaluator(evaluator_id, ...)` |\n| Run evaluator | `run_evaluator(evaluator_id, dataset_id or log_ids)` |\n\n---\n\n## Experiments\n\nAn experiment runs evaluators across a dataset to compare prompt/model configurations.\n\n### Workflow\n\n1. Create a dataset with test rows\n2. Create evaluator(s) to score outputs\n3. Create an experiment linking dataset + evaluators\n4. Run the experiment \u2014 it generates outputs for each row and scores them\n5. Compare results across configurations\n\n### Create an experiment\n\n```\nUse create_experiment:\n name: \"v2_vs_v3_comparison\"\n dataset_id: \"<dataset-id>\"\n evaluator_slugs: [\"response-quality\", \"hallucination-check\"]\n workflows:\n - type: \"prompt\"\n config: { prompt_id: \"<prompt-v2-id>\" }\n - type: \"prompt\"\n config: { prompt_id: \"<prompt-v3-id>\" }\n```\n\n### Workflow types\n\n| Type | Description |\n|------|-------------|\n| `prompt` | Run a saved prompt template against dataset rows |\n| `completion` | Run a model completion with config |\n| `custom` | User-defined logic |\n\n### MCP tools\n\n| Action | Tool |\n|--------|------|\n| List experiments | `list_experiments` |\n| Get experiment | `get_experiment(experiment_id)` |\n| Create experiment | `create_experiment(name, dataset_id, ...)` |\n| List experiment spans | `list_experiment_spans(experiment_id)` |\n| Get experiment span | `get_experiment_span(experiment_id, log_id)` |\n| Update experiment span | `update_experiment_span(experiment_id, log_id, ...)` |\n| Get summary stats | `get_experiment_spans_summary(experiment_id, start_time, end_time)` |\n\n---\n\n## End-to-End Example\n\n```\nStep 1: Create dataset\n create_dataset(name=\"qa_testset\")\n create_dataset_span(dataset_id=\"...\", input=\"What is Python?\", output=\"A programming language\")\n create_dataset_span(dataset_id=\"...\", input=\"Who made Rust?\", output=\"Mozilla Research\")\n\nStep 2: Create evaluator\n create_evaluator(\n name=\"accuracy_check\",\n score_value_type=\"boolean\",\n type=\"llm\",\n evaluator_slug=\"accuracy-check\"\n )\n\nStep 3: Create experiment\n create_experiment(\n name=\"gpt4o_vs_claude\",\n dataset_id=\"...\",\n evaluator_slugs=[\"accuracy-check\"]\n )\n\nStep 4: Review results\n list_experiment_spans(experiment_id=\"...\")\n get_experiment_spans_summary(experiment_id=\"...\", start_time=\"...\", end_time=\"...\")\n```\n\n---\n\n## CLI Commands\n\n```bash\nrespan datasets list # List datasets\nrespan datasets create --name <name> # Create a dataset\nrespan datasets get <id> # Get dataset details\nrespan datasets spans <id> # List dataset spans\nrespan evaluators list # List evaluators\nrespan evaluators get <id> # Get evaluator details\nrespan evaluators create # Create an evaluator\nrespan evaluators run <id> # Run an evaluator\nrespan experiments list # List experiments\nrespan experiments get <id> # Get experiment details\nrespan experiments create # Create an experiment\nrespan eval <file> # Run a one-shot eval defined in a JSON file\n```\n\n## One-Shot Evals via JSON\n\n`respan eval <file.json>` runs a complete eval pipeline from a single JSON spec \u2014\ncreates the prompt, dataset, evaluators, and experiment in one command.\nIDs are written back into the file so reruns reuse existing resources.\n\n### Minimal example\n\n```json\n{\n \"name\": \"Movie matcher\",\n \"prompt\": {\n \"name\": \"movie-matcher\",\n \"model\": \"gpt-5-mini\",\n \"messages\": [\n { \"role\": \"system\", \"content\": \"Identify the movie. Reply with only the title.\" },\n { \"role\": \"user\", \"content\": \"{{description}}\" }\n ]\n },\n \"dataset\": {\n \"name\": \"movie-descriptions\",\n \"rows\": [\n { \"description\": \"A detective investigates seven deadly sins.\", \"expected_output\": \"Se7en\" },\n { \"description\": \"A hacker learns reality is a simulation.\", \"expected_output\": \"The Matrix\" }\n ]\n },\n \"experiment\": {\n \"evaluators\": [\n {\n \"name\": \"Title match\",\n \"type\": \"llm\",\n \"score_value_type\": \"boolean\",\n \"llm_config\": {\n \"model\": \"openai/gpt-5-mini\",\n \"evaluator_definition\": \"Expected: {{expected_output}}. Got: {{output}}. Correct? Reply true or false.\"\n }\n }\n ]\n }\n}\n```\n\nRun:\n\n```bash\nrespan eval movie-matcher.eval.json\n```\n\nThe runner creates the prompt (and deploys version 1), creates the dataset (and bulk-loads rows),\ncreates each evaluator + its workflow (and deploys), then creates the experiment linking them.\nEach step writes the resulting IDs back to the JSON file so subsequent runs are idempotent \u2014\nre-running with the same file reuses the existing resources.\n";
@@ -91,16 +91,18 @@ Check higher-priority categories first. If a match is found, use that instrument
91
91
  | OpenAI Agents SDK | \`openai-agents\` | \`@openai/agents\` | \`respan-instrumentation-openai-agents\` | \`@respan/instrumentation-openai-agents\` | [docs](https://respan.ai/docs/integrations/openai-agents-sdk.md) |
92
92
  | Claude Agent SDK | \`claude-agent-sdk\` | — | \`respan-instrumentation-claude-agent-sdk\` | — | [docs](https://respan.ai/docs/integrations/claude-agents-sdk.md) |
93
93
  | Pydantic AI | \`pydantic-ai\` | — | \`respan-instrumentation-pydantic-ai\` | — | [docs](https://respan.ai/docs/integrations/pydantic-ai.md) |
94
- | LangChain | \`langchain\` | \`langchain\` | via OpenInference | — | [docs](https://respan.ai/docs/integrations/langchain.md) |
95
- | LangGraph | \`langgraph\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/langgraph.md) |
94
+ | LangChain | \`langchain\` | \`langchain\` | \`respan-instrumentation-langchain\` (callback) | — | [docs](https://respan.ai/docs/integrations/langchain.md) |
95
+ | LangGraph | \`langgraph\` | — | \`respan-instrumentation-langchain\` (callback) | — | [docs](https://respan.ai/docs/integrations/langgraph.md) |
96
96
  | CrewAI | \`crewai\` | — | \`respan-instrumentation-crewai\` | — | [docs](https://respan.ai/docs/integrations/crewai.md) |
97
- | LlamaIndex | \`llama-index\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/llama-index.md) |
97
+ | LlamaIndex | \`llama-index\` | — | \`respan-instrumentation-llama-index\` | — | [docs](https://respan.ai/docs/integrations/llama-index.md) |
98
98
  | Haystack | \`haystack-ai\` | — | \`respan-instrumentation-haystack\` | — | [docs](https://respan.ai/docs/integrations/haystack.md) |
99
99
  | Mastra | — | \`mastra\` | — | via OTEL | [docs](https://respan.ai/docs/integrations/mastra.md) |
100
- | Google ADK | \`google-adk\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/google-adk.md) |
100
+ | Google ADK | \`google-adk\` | — | \`respan-instrumentation-google-adk\` | — | [docs](https://respan.ai/docs/integrations/google-adk.md) |
101
101
 
102
102
  If a Priority 1 framework is found, use its instrumentation. Do NOT also add Priority 2 instrumentation for the same provider.
103
103
 
104
+ **LangChain / LangGraph use a callback pattern, not \`Respan(instrumentations=[...])\`.** Set up \`RespanTelemetry\` and pass \`add_respan_callback(config=...)\` into the chain/graph invocation: \`from respan_tracing import RespanTelemetry\` + \`from respan_instrumentation_langchain import add_respan_callback\`. (The other Priority-1 frameworks use the standard \`Respan(instrumentations=[XInstrumentor()])\` plugin pattern.)
105
+
104
106
  **Priority 2 — Direct LLM SDKs** (only if no P1 framework covers this provider):
105
107
 
106
108
  These are **auto-instrumented** — just \`Respan()\` / \`new Respan()\`, no extra packages needed: