@amplitude/ai 0.1.2 → 0.2.1

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.
Files changed (59) hide show
  1. package/AGENTS.md +3 -1
  2. package/README.md +44 -15
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +2 -1
  5. package/dist/client.js.map +1 -1
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.js +2 -2
  8. package/dist/integrations/anthropic-tools.js +2 -1
  9. package/dist/integrations/anthropic-tools.js.map +1 -1
  10. package/dist/integrations/langchain.d.ts.map +1 -1
  11. package/dist/integrations/langchain.js +35 -5
  12. package/dist/integrations/langchain.js.map +1 -1
  13. package/dist/integrations/llamaindex.d.ts.map +1 -1
  14. package/dist/integrations/llamaindex.js +27 -4
  15. package/dist/integrations/llamaindex.js.map +1 -1
  16. package/dist/integrations/openai-agents.js +5 -1
  17. package/dist/integrations/openai-agents.js.map +1 -1
  18. package/dist/integrations/opentelemetry.d.ts.map +1 -1
  19. package/dist/integrations/opentelemetry.js +2 -1
  20. package/dist/integrations/opentelemetry.js.map +1 -1
  21. package/dist/mcp/patterns.d.ts.map +1 -1
  22. package/dist/mcp/patterns.js +6 -0
  23. package/dist/mcp/patterns.js.map +1 -1
  24. package/dist/mcp/validate-file.js +1 -1
  25. package/dist/mcp/validate-file.js.map +1 -1
  26. package/dist/patching.d.ts.map +1 -1
  27. package/dist/patching.js +7 -1
  28. package/dist/patching.js.map +1 -1
  29. package/dist/providers/anthropic.d.ts.map +1 -1
  30. package/dist/providers/anthropic.js +7 -2
  31. package/dist/providers/anthropic.js.map +1 -1
  32. package/dist/providers/bedrock.d.ts.map +1 -1
  33. package/dist/providers/bedrock.js +4 -2
  34. package/dist/providers/bedrock.js.map +1 -1
  35. package/dist/providers/gemini.d.ts.map +1 -1
  36. package/dist/providers/gemini.js +4 -2
  37. package/dist/providers/gemini.js.map +1 -1
  38. package/dist/providers/mistral.d.ts.map +1 -1
  39. package/dist/providers/mistral.js +4 -2
  40. package/dist/providers/mistral.js.map +1 -1
  41. package/dist/providers/openai.d.ts.map +1 -1
  42. package/dist/providers/openai.js +14 -4
  43. package/dist/providers/openai.js.map +1 -1
  44. package/dist/session.d.ts +23 -0
  45. package/dist/session.d.ts.map +1 -1
  46. package/dist/session.js +44 -1
  47. package/dist/session.js.map +1 -1
  48. package/dist/utils/costs.d.ts +46 -5
  49. package/dist/utils/costs.d.ts.map +1 -1
  50. package/dist/utils/costs.js +119 -26
  51. package/dist/utils/costs.js.map +1 -1
  52. package/dist/utils/providers.d.ts +6 -1
  53. package/dist/utils/providers.d.ts.map +1 -1
  54. package/dist/utils/providers.js +9 -3
  55. package/dist/utils/providers.js.map +1 -1
  56. package/llms-full.txt +17 -1
  57. package/llms.txt +1 -1
  58. package/mcp.schema.json +1 -1
  59. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","names":["endOpts: SessionEndOpts"],"sources":["../src/session.ts"],"sourcesContent":["/**\n * Session context manager using Node.js AsyncLocalStorage.\n *\n * Use `.run()` to execute code within session context. The session\n * auto-ends when the callback completes, emitting `[Agent] Session End`.\n *\n * @example\n * ```typescript\n * const session = agent.session();\n * await session.run(async (s) => {\n * s.trackUserMessage('What is retention?');\n * s.trackAiMessage('Retention is...', 'gpt-4', 'openai', 200);\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n AiMessageOpts,\n BoundAgent,\n EmbeddingOpts,\n ScoreOpts,\n SessionEndOpts,\n SpanOpts,\n ToolCallOpts,\n UserMessageOpts,\n} from './bound-agent.js';\nimport {\n _sessionStorage,\n getActiveContext,\n SessionContext,\n} from './context.js';\nimport type { SessionEnrichments } from './core/enrichments.js';\nimport { PROP_SESSION_REPLAY_ID } from './core/tracking.js';\nimport { getLogger } from './utils/logger.js';\n\nexport class Session {\n readonly sessionId: string;\n traceId: string | null = null;\n readonly idleTimeoutMinutes: number | null;\n readonly userId: string | null;\n readonly deviceId: string | null;\n readonly browserSessionId: string | null;\n private _agent: BoundAgent;\n private _enrichments: SessionEnrichments | null = null;\n private _sessionReplayId: string | null;\n\n constructor(\n agent: BoundAgent,\n opts: {\n sessionId?: string | null;\n idleTimeoutMinutes?: number | null;\n userId?: string | null;\n deviceId?: string | null;\n browserSessionId?: string | null;\n } = {},\n ) {\n this.sessionId = opts.sessionId ?? randomUUID();\n this.idleTimeoutMinutes = opts.idleTimeoutMinutes ?? null;\n this.userId = opts.userId ?? null;\n this.deviceId =\n opts.deviceId ?? (agent._defaults.deviceId as string | null);\n this.browserSessionId =\n opts.browserSessionId ??\n (agent._defaults.browserSessionId as string | null);\n this._agent = agent;\n this._sessionReplayId =\n this.deviceId && this.browserSessionId\n ? `${this.deviceId}/${this.browserSessionId}`\n : null;\n }\n\n private _buildSessionContext(): SessionContext {\n const defaults = this._agent._defaults;\n const ai = this._agent._ai;\n const sid = this.sessionId;\n\n return new SessionContext({\n sessionId: sid,\n traceId: this.traceId,\n userId: (this.userId ?? defaults.userId) as string | null,\n agentId: defaults.agentId as string | null,\n parentAgentId: defaults.parentAgentId as string | null,\n env: defaults.env as string | null,\n customerOrgId: defaults.customerOrgId as string | null,\n agentVersion: defaults.agentVersion as string | null,\n context: defaults.context as Record<string, unknown> | null,\n groups: defaults.groups as Record<string, unknown> | null,\n idleTimeoutMinutes: this.idleTimeoutMinutes,\n deviceId: this.deviceId ?? (defaults.deviceId as string | null),\n browserSessionId:\n this.browserSessionId ?? (defaults.browserSessionId as string | null),\n nextTurnIdFn: () => ai._nextTurnId(sid),\n });\n }\n\n newTrace(): string {\n this.traceId = randomUUID();\n const ctx = getActiveContext();\n if (ctx != null) {\n ctx.traceId = this.traceId;\n }\n return this.traceId;\n }\n\n setEnrichments(enrichments: SessionEnrichments): void {\n this._enrichments = enrichments;\n }\n\n /**\n * Run a callback within this session context.\n * This is the Node.js equivalent of Python's `with session as s:` block.\n */\n async run<T>(fn: (session: Session) => T | Promise<T>): Promise<T> {\n const ctx = this._buildSessionContext();\n try {\n const result = await _sessionStorage.run(ctx, () => fn(this));\n return result;\n } finally {\n this._autoEnd();\n }\n }\n\n /**\n * Synchronous version of run() for non-async code.\n */\n runSync<T>(fn: (session: Session) => T): T {\n const ctx = this._buildSessionContext();\n try {\n return _sessionStorage.run(ctx, () => fn(this));\n } finally {\n this._autoEnd();\n }\n }\n\n private _autoEnd(): void {\n try {\n const endOpts: SessionEndOpts = {\n sessionId: this.sessionId,\n enrichments: this._enrichments,\n idleTimeoutMinutes: this.idleTimeoutMinutes,\n };\n if (this.userId != null) endOpts.userId = this.userId;\n this._agent.trackSessionEnd(this._inject(endOpts));\n } catch (e) {\n getLogger().debug(`Failed to auto-end session ${this.sessionId}: ${e}`);\n }\n }\n\n private _inject<T extends Record<string, unknown>>(kwargs: T): T {\n const merged = { ...kwargs } as Record<string, unknown>;\n if (merged.sessionId == null) merged.sessionId = this.sessionId;\n if (this.traceId != null && merged.traceId == null)\n merged.traceId = this.traceId;\n if (this.userId != null && merged.userId == null)\n merged.userId = this.userId;\n if (this._sessionReplayId != null) {\n const existingEp = merged.eventProperties as\n | Record<string, unknown>\n | undefined;\n const ep = existingEp != null ? { ...existingEp } : {};\n if (!(PROP_SESSION_REPLAY_ID in ep)) {\n ep[PROP_SESSION_REPLAY_ID] = this._sessionReplayId;\n merged.eventProperties = ep;\n }\n }\n return merged as T;\n }\n\n trackUserMessage(content: string, opts: UserMessageOpts = {}): string {\n return this._agent.trackUserMessage(content, this._inject(opts));\n }\n\n trackAiMessage(\n content: string,\n model: string,\n provider: string,\n latencyMs: number,\n opts: AiMessageOpts = {},\n ): string {\n return this._agent.trackAiMessage(\n content,\n model,\n provider,\n latencyMs,\n this._inject(opts),\n );\n }\n\n trackToolCall(\n toolName: string,\n latencyMs: number,\n success: boolean,\n opts: ToolCallOpts = {},\n ): string {\n return this._agent.trackToolCall(\n toolName,\n latencyMs,\n success,\n this._inject(opts),\n );\n }\n\n trackEmbedding(\n model: string,\n provider: string,\n latencyMs: number,\n opts: EmbeddingOpts = {},\n ): string {\n return this._agent.trackEmbedding(\n model,\n provider,\n latencyMs,\n this._inject(opts),\n );\n }\n\n trackSpan(spanName: string, latencyMs: number, opts: SpanOpts = {}): string {\n return this._agent.trackSpan(spanName, latencyMs, this._inject(opts));\n }\n\n score(\n name: string,\n value: number,\n targetId: string,\n opts: ScoreOpts = {},\n ): void {\n this._agent.score(name, value, targetId, this._inject(opts));\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session.js","names":["endOpts: SessionEndOpts"],"sources":["../src/session.ts"],"sourcesContent":["/**\n * Session context manager using Node.js AsyncLocalStorage.\n *\n * Use `.run()` to execute code within session context. The session\n * auto-ends when the callback completes, emitting `[Agent] Session End`.\n *\n * @example\n * ```typescript\n * const session = agent.session();\n * await session.run(async (s) => {\n * s.trackUserMessage('What is retention?');\n * s.trackAiMessage('Retention is...', 'gpt-4', 'openai', 200);\n * });\n * ```\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n AiMessageOpts,\n BoundAgent,\n EmbeddingOpts,\n ScoreOpts,\n SessionEndOpts,\n SpanOpts,\n ToolCallOpts,\n UserMessageOpts,\n} from './bound-agent.js';\nimport {\n _sessionStorage,\n getActiveContext,\n SessionContext,\n} from './context.js';\nimport type { SessionEnrichments } from './core/enrichments.js';\nimport { PROP_SESSION_REPLAY_ID } from './core/tracking.js';\nimport { getLogger } from './utils/logger.js';\n\nexport class Session {\n readonly sessionId: string;\n traceId: string | null = null;\n readonly idleTimeoutMinutes: number | null;\n readonly userId: string | null;\n readonly deviceId: string | null;\n readonly browserSessionId: string | null;\n private _agent: BoundAgent;\n private _enrichments: SessionEnrichments | null = null;\n private _sessionReplayId: string | null;\n\n constructor(\n agent: BoundAgent,\n opts: {\n sessionId?: string | null;\n idleTimeoutMinutes?: number | null;\n userId?: string | null;\n deviceId?: string | null;\n browserSessionId?: string | null;\n } = {},\n ) {\n this.sessionId = opts.sessionId ?? randomUUID();\n this.idleTimeoutMinutes = opts.idleTimeoutMinutes ?? null;\n this.userId = opts.userId ?? null;\n this.deviceId =\n opts.deviceId ?? (agent._defaults.deviceId as string | null);\n this.browserSessionId =\n opts.browserSessionId ??\n (agent._defaults.browserSessionId as string | null);\n this._agent = agent;\n this._sessionReplayId =\n this.deviceId && this.browserSessionId\n ? `${this.deviceId}/${this.browserSessionId}`\n : null;\n }\n\n private _buildSessionContext(): SessionContext {\n const defaults = this._agent._defaults;\n const ai = this._agent._ai;\n const sid = this.sessionId;\n\n return new SessionContext({\n sessionId: sid,\n traceId: this.traceId,\n userId: (this.userId ?? defaults.userId) as string | null,\n agentId: defaults.agentId as string | null,\n parentAgentId: defaults.parentAgentId as string | null,\n env: defaults.env as string | null,\n customerOrgId: defaults.customerOrgId as string | null,\n agentVersion: defaults.agentVersion as string | null,\n context: defaults.context as Record<string, unknown> | null,\n groups: defaults.groups as Record<string, unknown> | null,\n idleTimeoutMinutes: this.idleTimeoutMinutes,\n deviceId: this.deviceId ?? (defaults.deviceId as string | null),\n browserSessionId:\n this.browserSessionId ?? (defaults.browserSessionId as string | null),\n nextTurnIdFn: () => ai._nextTurnId(sid),\n });\n }\n\n newTrace(): string {\n this.traceId = randomUUID();\n const ctx = getActiveContext();\n if (ctx != null) {\n ctx.traceId = this.traceId;\n }\n return this.traceId;\n }\n\n setEnrichments(enrichments: SessionEnrichments): void {\n this._enrichments = enrichments;\n }\n\n /**\n * Run a callback within this session context.\n * This is the Node.js equivalent of Python's `with session as s:` block.\n */\n async run<T>(fn: (session: Session) => T | Promise<T>): Promise<T> {\n const ctx = this._buildSessionContext();\n try {\n const result = await _sessionStorage.run(ctx, () => fn(this));\n return result;\n } finally {\n this._autoEnd();\n }\n }\n\n /**\n * Synchronous version of run() for non-async code.\n */\n runSync<T>(fn: (session: Session) => T): T {\n const ctx = this._buildSessionContext();\n try {\n return _sessionStorage.run(ctx, () => fn(this));\n } finally {\n this._autoEnd();\n }\n }\n\n /**\n * Run a callback as a child agent within this session.\n *\n * Provider wrappers automatically pick up the child agent's identity\n * (`agentId`, `parentAgentId`) while sharing this session's `sessionId`,\n * `traceId`, and turn counter. No `[Agent] Session End` is emitted.\n *\n * @example\n * ```typescript\n * const child = parentAgent.child('researcher');\n * await session.run(async (s) => {\n * const result = await s.runAs(child, async (cs) => {\n * // provider wrappers see agentId='researcher'\n * return openai.chat.completions.create({ ... });\n * });\n * });\n * ```\n */\n async runAs<T>(\n childAgent: BoundAgent,\n fn: (session: Session) => T | Promise<T>,\n ): Promise<T> {\n const childSession = new Session(childAgent, {\n sessionId: this.sessionId,\n userId: this.userId,\n deviceId: this.deviceId,\n browserSessionId: this.browserSessionId,\n });\n childSession.traceId = this.traceId;\n const ctx = childSession._buildSessionContext();\n return await _sessionStorage.run(ctx, () => fn(childSession));\n }\n\n /**\n * Synchronous version of {@link runAs}.\n */\n runAsSync<T>(\n childAgent: BoundAgent,\n fn: (session: Session) => T,\n ): T {\n const childSession = new Session(childAgent, {\n sessionId: this.sessionId,\n userId: this.userId,\n deviceId: this.deviceId,\n browserSessionId: this.browserSessionId,\n });\n childSession.traceId = this.traceId;\n const ctx = childSession._buildSessionContext();\n return _sessionStorage.run(ctx, () => fn(childSession));\n }\n\n private _autoEnd(): void {\n try {\n const endOpts: SessionEndOpts = {\n sessionId: this.sessionId,\n enrichments: this._enrichments,\n idleTimeoutMinutes: this.idleTimeoutMinutes,\n };\n if (this.userId != null) endOpts.userId = this.userId;\n this._agent.trackSessionEnd(this._inject(endOpts));\n } catch (e) {\n getLogger().debug(`Failed to auto-end session ${this.sessionId}: ${e}`);\n }\n }\n\n private _inject<T extends Record<string, unknown>>(kwargs: T): T {\n const merged = { ...kwargs } as Record<string, unknown>;\n if (merged.sessionId == null) merged.sessionId = this.sessionId;\n if (this.traceId != null && merged.traceId == null)\n merged.traceId = this.traceId;\n if (this.userId != null && merged.userId == null)\n merged.userId = this.userId;\n if (this._sessionReplayId != null) {\n const existingEp = merged.eventProperties as\n | Record<string, unknown>\n | undefined;\n const ep = existingEp != null ? { ...existingEp } : {};\n if (!(PROP_SESSION_REPLAY_ID in ep)) {\n ep[PROP_SESSION_REPLAY_ID] = this._sessionReplayId;\n merged.eventProperties = ep;\n }\n }\n return merged as T;\n }\n\n trackUserMessage(content: string, opts: UserMessageOpts = {}): string {\n return this._agent.trackUserMessage(content, this._inject(opts));\n }\n\n trackAiMessage(\n content: string,\n model: string,\n provider: string,\n latencyMs: number,\n opts: AiMessageOpts = {},\n ): string {\n return this._agent.trackAiMessage(\n content,\n model,\n provider,\n latencyMs,\n this._inject(opts),\n );\n }\n\n trackToolCall(\n toolName: string,\n latencyMs: number,\n success: boolean,\n opts: ToolCallOpts = {},\n ): string {\n return this._agent.trackToolCall(\n toolName,\n latencyMs,\n success,\n this._inject(opts),\n );\n }\n\n trackEmbedding(\n model: string,\n provider: string,\n latencyMs: number,\n opts: EmbeddingOpts = {},\n ): string {\n return this._agent.trackEmbedding(\n model,\n provider,\n latencyMs,\n this._inject(opts),\n );\n }\n\n trackSpan(spanName: string, latencyMs: number, opts: SpanOpts = {}): string {\n return this._agent.trackSpan(spanName, latencyMs, this._inject(opts));\n }\n\n score(\n name: string,\n value: number,\n targetId: string,\n opts: ScoreOpts = {},\n ): void {\n this._agent.score(name, value, targetId, this._inject(opts));\n }\n}\n"],"mappings":"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"}
@@ -2,6 +2,20 @@ import { inferProviderFromModel } from "./providers.js";
2
2
 
3
3
  //#region src/utils/costs.d.ts
4
4
 
5
+ /**
6
+ * Opt in to background price updates from the genai-prices GitHub repo.
7
+ *
8
+ * Call once at application startup (e.g. after `AmplitudeAI` init) to fetch
9
+ * the latest pricing data periodically. This ensures new model pricing is
10
+ * available within days of being added to the genai-prices repository,
11
+ * instead of waiting for an npm package release.
12
+ *
13
+ * This makes outbound HTTPS requests to raw.githubusercontent.com.
14
+ * Only enable in environments where outbound network access is permitted.
15
+ *
16
+ * @param intervalMs - refresh interval in milliseconds (default: 1 hour)
17
+ */
18
+ declare function enableLivePriceUpdates(intervalMs?: number): void;
5
19
  declare function stripProviderPrefix(modelName: string): string;
6
20
  /**
7
21
  * Infer the provider name from a model name.
@@ -9,19 +23,46 @@ declare function stripProviderPrefix(modelName: string): string;
9
23
  */
10
24
  declare const inferProvider: typeof inferProviderFromModel;
11
25
  /**
12
- * Generate candidate model names for price lookup, mirroring Python's
13
- * get_genai_price_lookup_candidates(). Tries progressively stripped names
14
- * so the caller can attempt each until a match is found.
26
+ * Generate candidate (modelRef, providerId) pairs for price lookup.
27
+ *
28
+ * For Bedrock/AWS models, uses a **generalized** dot-prefix stripping strategy
29
+ * instead of enumerating known regions or vendors. Bedrock model IDs follow
30
+ * `[region.][vendor.]model-name[-version]` — we progressively strip
31
+ * dot-separated prefixes and try each variant with and without provider,
32
+ * plus `regional.` / `global.` prefixes that genai-prices uses.
33
+ *
34
+ * This approach is forward-compatible: new AWS regions and Bedrock vendors
35
+ * work automatically without code changes.
36
+ */
37
+ declare function getGenaiPriceLookupCandidates(modelName: string, defaultProvider?: string): Array<{
38
+ model: string;
39
+ providerId?: string;
40
+ }>;
41
+ /**
42
+ * Calculate cost for an LLM call using genai-prices.
43
+ *
44
+ * IMPORTANT CONTRACT:
45
+ * - `inputTokens` MUST be the TOTAL input token count (including cached tokens).
46
+ * For Anthropic: raw input_tokens + cache_read + cache_creation.
47
+ * For OpenAI: prompt_tokens already includes cached_tokens.
48
+ * - `outputTokens` MUST be the TOTAL output token count (including reasoning tokens).
49
+ * For OpenAI: completion_tokens already includes reasoning_tokens.
50
+ * Do NOT pass reasoning tokens separately and then add them here.
51
+ * - `cacheReadInputTokens` and `cacheCreationInputTokens` are SUBSETS of inputTokens,
52
+ * used only for differential pricing (cached tokens are cheaper).
53
+ * - `reasoningTokens` is IGNORED for cost calculation — it exists only for backward
54
+ * compatibility. Reasoning tokens are already included in outputTokens.
15
55
  */
16
- declare function getGenaiPriceLookupCandidates(modelName: string): string[];
17
56
  declare function calculateCost(options: {
18
57
  modelName: string;
19
58
  inputTokens: number;
20
59
  outputTokens: number;
60
+ /** @deprecated Ignored — reasoning tokens are already included in outputTokens. */
21
61
  reasoningTokens?: number;
22
62
  cacheReadInputTokens?: number;
23
63
  cacheCreationInputTokens?: number;
64
+ defaultProvider?: string;
24
65
  }): number;
25
66
  //#endregion
26
- export { calculateCost, getGenaiPriceLookupCandidates, inferProvider, stripProviderPrefix };
67
+ export { calculateCost, enableLivePriceUpdates, getGenaiPriceLookupCandidates, inferProvider, stripProviderPrefix };
27
68
  //# sourceMappingURL=costs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"costs.d.ts","names":[],"sources":["../../src/utils/costs.ts"],"sourcesContent":[],"mappings":";;;;iBAYgB,mBAAA;;;;;cAgBH,sBAAa;;;;;;iBAOV,6BAAA;iBAmBA,aAAA"}
1
+ {"version":3,"file":"costs.d.ts","names":[],"sources":["../../src/utils/costs.ts"],"sourcesContent":[],"mappings":";;;;AAmLA;;;;;;;;;;;;;iBArJgB,sBAAA;iBAiCA,mBAAA;;;;;cASH,sBAAa;;;;;;;;;;;;;iBAqBV,6BAAA,+CAGb;;;;;;;;;;;;;;;;;;;iBAmFa,aAAA"}
@@ -1,4 +1,4 @@
1
- import { inferProviderFromModel } from "./providers.js";
1
+ import { inferProviderFromModel, tryInferProviderFromModel } from "./providers.js";
2
2
  import { tryRequire } from "./resolve-module.js";
3
3
 
4
4
  //#region src/utils/costs.ts
@@ -9,55 +9,148 @@ import { tryRequire } from "./resolve-module.js";
9
9
  * Falls back to returning 0 when not installed.
10
10
  */
11
11
  const genaiPrices = tryRequire("@pydantic/genai-prices");
12
+ let _livePricesEnabled = false;
13
+ /**
14
+ * Opt in to background price updates from the genai-prices GitHub repo.
15
+ *
16
+ * Call once at application startup (e.g. after `AmplitudeAI` init) to fetch
17
+ * the latest pricing data periodically. This ensures new model pricing is
18
+ * available within days of being added to the genai-prices repository,
19
+ * instead of waiting for an npm package release.
20
+ *
21
+ * This makes outbound HTTPS requests to raw.githubusercontent.com.
22
+ * Only enable in environments where outbound network access is permitted.
23
+ *
24
+ * @param intervalMs - refresh interval in milliseconds (default: 1 hour)
25
+ */
26
+ function enableLivePriceUpdates(intervalMs = 36e5) {
27
+ if (_livePricesEnabled || genaiPrices == null) return;
28
+ _livePricesEnabled = true;
29
+ const prices = genaiPrices;
30
+ if (typeof prices.updatePrices !== "function") return;
31
+ const doUpdate = () => {
32
+ try {
33
+ prices.updatePrices(async ({ remoteDataUrl, setProviderData }) => {
34
+ try {
35
+ const resp = await fetch(remoteDataUrl);
36
+ if (resp.ok) setProviderData(await resp.json());
37
+ } catch {}
38
+ });
39
+ } catch {}
40
+ };
41
+ doUpdate();
42
+ setInterval(doUpdate, intervalMs).unref?.();
43
+ }
12
44
  function stripProviderPrefix(modelName) {
13
45
  const colonIdx = modelName.indexOf(":");
14
46
  return colonIdx >= 0 ? modelName.slice(colonIdx + 1) : modelName;
15
47
  }
16
- function normalizeBedrockModel(modelName) {
17
- return modelName.match(/(?:us\.|eu\.|ap\.)?(?:anthropic|meta|mistral|amazon|cohere)\.(.*)/)?.[1] ?? modelName;
18
- }
19
48
  /**
20
49
  * Infer the provider name from a model name.
21
50
  * Delegates to the canonical implementation in utils/providers.ts.
22
51
  */
23
52
  const inferProvider = inferProviderFromModel;
53
+ function normalizeProviderForGenaiPrices(provider) {
54
+ if (provider === "gemini") return "google";
55
+ return provider;
56
+ }
24
57
  /**
25
- * Generate candidate model names for price lookup, mirroring Python's
26
- * get_genai_price_lookup_candidates(). Tries progressively stripped names
27
- * so the caller can attempt each until a match is found.
58
+ * Generate candidate (modelRef, providerId) pairs for price lookup.
59
+ *
60
+ * For Bedrock/AWS models, uses a **generalized** dot-prefix stripping strategy
61
+ * instead of enumerating known regions or vendors. Bedrock model IDs follow
62
+ * `[region.][vendor.]model-name[-version]` — we progressively strip
63
+ * dot-separated prefixes and try each variant with and without provider,
64
+ * plus `regional.` / `global.` prefixes that genai-prices uses.
65
+ *
66
+ * This approach is forward-compatible: new AWS regions and Bedrock vendors
67
+ * work automatically without code changes.
28
68
  */
29
- function getGenaiPriceLookupCandidates(modelName) {
30
- const candidates = [];
69
+ function getGenaiPriceLookupCandidates(modelName, defaultProvider) {
31
70
  const stripped = stripProviderPrefix(modelName);
32
- const normalized = normalizeBedrockModel(stripped);
33
- if (normalized !== modelName) candidates.push(normalized);
34
- if (stripped !== modelName && stripped !== normalized) candidates.push(stripped);
35
- candidates.push(modelName);
36
- return [...new Set(candidates)];
71
+ const inferred = defaultProvider ?? tryInferProviderFromModel(stripped);
72
+ const isBedrock = inferred === "bedrock" || defaultProvider === "bedrock" || modelName.startsWith("bedrock:");
73
+ const providerId = isBedrock ? "aws" : normalizeProviderForGenaiPrices(inferred);
74
+ const candidates = [{
75
+ model: stripped,
76
+ providerId
77
+ }];
78
+ if (isBedrock) candidates.push({
79
+ model: stripped,
80
+ providerId: void 0
81
+ });
82
+ if (stripped.includes(".")) {
83
+ const parts = stripped.split(".");
84
+ for (let i = 1; i < parts.length; i++) {
85
+ const sub = parts.slice(i).join(".");
86
+ candidates.push({
87
+ model: sub,
88
+ providerId
89
+ });
90
+ candidates.push({ model: sub });
91
+ }
92
+ if (isBedrock) {
93
+ let vendorModel = stripped;
94
+ const firstSeg = parts[0];
95
+ if (firstSeg !== "regional" && firstSeg !== "global" && parts.length > 2) vendorModel = parts.slice(1).join(".");
96
+ if (!vendorModel.startsWith("regional.") && !vendorModel.startsWith("global.")) {
97
+ candidates.push({ model: `regional.${vendorModel}` });
98
+ candidates.push({ model: `global.${vendorModel}` });
99
+ }
100
+ }
101
+ }
102
+ const seen = /* @__PURE__ */ new Set();
103
+ return candidates.filter((c) => {
104
+ const key = `${c.model}::${c.providerId ?? ""}`;
105
+ if (seen.has(key)) return false;
106
+ seen.add(key);
107
+ return true;
108
+ });
37
109
  }
38
110
  function safeInt(value) {
39
111
  if (typeof value === "number" && !Number.isNaN(value)) return Math.round(value);
40
112
  return 0;
41
113
  }
114
+ /**
115
+ * Calculate cost for an LLM call using genai-prices.
116
+ *
117
+ * IMPORTANT CONTRACT:
118
+ * - `inputTokens` MUST be the TOTAL input token count (including cached tokens).
119
+ * For Anthropic: raw input_tokens + cache_read + cache_creation.
120
+ * For OpenAI: prompt_tokens already includes cached_tokens.
121
+ * - `outputTokens` MUST be the TOTAL output token count (including reasoning tokens).
122
+ * For OpenAI: completion_tokens already includes reasoning_tokens.
123
+ * Do NOT pass reasoning tokens separately and then add them here.
124
+ * - `cacheReadInputTokens` and `cacheCreationInputTokens` are SUBSETS of inputTokens,
125
+ * used only for differential pricing (cached tokens are cheaper).
126
+ * - `reasoningTokens` is IGNORED for cost calculation — it exists only for backward
127
+ * compatibility. Reasoning tokens are already included in outputTokens.
128
+ */
42
129
  function calculateCost(options) {
43
- const { modelName, inputTokens, outputTokens, reasoningTokens = 0, cacheReadInputTokens = 0, cacheCreationInputTokens = 0 } = options;
130
+ const { modelName, inputTokens, outputTokens, cacheReadInputTokens = 0, cacheCreationInputTokens = 0, defaultProvider } = options;
44
131
  if (genaiPrices != null) try {
45
132
  const prices = genaiPrices;
46
- if (typeof prices.calculateCost === "function") {
47
- const normalized = normalizeBedrockModel(stripProviderPrefix(modelName));
48
- return prices.calculateCost({
49
- model: normalized,
50
- inputTokens: safeInt(inputTokens),
51
- outputTokens: safeInt(outputTokens),
52
- reasoningTokens: safeInt(reasoningTokens),
53
- cacheReadInputTokens: safeInt(cacheReadInputTokens),
54
- cacheCreationInputTokens: safeInt(cacheCreationInputTokens)
55
- }) ?? 0;
133
+ if (typeof prices.calcPrice === "function") {
134
+ const calcPriceFn = prices.calcPrice;
135
+ const usage = {
136
+ input_tokens: safeInt(inputTokens),
137
+ output_tokens: safeInt(outputTokens),
138
+ cache_read_tokens: safeInt(cacheReadInputTokens),
139
+ cache_write_tokens: safeInt(cacheCreationInputTokens)
140
+ };
141
+ const candidates = getGenaiPriceLookupCandidates(modelName, defaultProvider);
142
+ for (const { model, providerId } of candidates) {
143
+ const opts = {};
144
+ if (providerId) opts.providerId = providerId;
145
+ const result = calcPriceFn(usage, model, Object.keys(opts).length > 0 ? opts : void 0);
146
+ if (result?.total_price != null && result.total_price > 0) return result.total_price;
147
+ }
148
+ return 0;
56
149
  }
57
150
  } catch {}
58
151
  return 0;
59
152
  }
60
153
 
61
154
  //#endregion
62
- export { calculateCost, getGenaiPriceLookupCandidates, inferProvider, stripProviderPrefix };
155
+ export { calculateCost, enableLivePriceUpdates, getGenaiPriceLookupCandidates, inferProvider, stripProviderPrefix };
63
156
  //# sourceMappingURL=costs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"costs.js","names":["candidates: string[]"],"sources":["../../src/utils/costs.ts"],"sourcesContent":["/**\n * Cost calculation utilities.\n *\n * Uses the genai-prices package when available (npm: @pydantic/genai-prices).\n * Falls back to returning 0 when not installed.\n */\n\nimport { inferProviderFromModel } from './providers.js';\nimport { tryRequire } from './resolve-module.js';\n\nconst genaiPrices = tryRequire('@pydantic/genai-prices');\n\nexport function stripProviderPrefix(modelName: string): string {\n const colonIdx = modelName.indexOf(':');\n return colonIdx >= 0 ? modelName.slice(colonIdx + 1) : modelName;\n}\n\nfunction normalizeBedrockModel(modelName: string): string {\n const match = modelName.match(\n /(?:us\\.|eu\\.|ap\\.)?(?:anthropic|meta|mistral|amazon|cohere)\\.(.*)/,\n );\n return match?.[1] ?? modelName;\n}\n\n/**\n * Infer the provider name from a model name.\n * Delegates to the canonical implementation in utils/providers.ts.\n */\nexport const inferProvider = inferProviderFromModel;\n\n/**\n * Generate candidate model names for price lookup, mirroring Python's\n * get_genai_price_lookup_candidates(). Tries progressively stripped names\n * so the caller can attempt each until a match is found.\n */\nexport function getGenaiPriceLookupCandidates(modelName: string): string[] {\n const candidates: string[] = [];\n const stripped = stripProviderPrefix(modelName);\n const normalized = normalizeBedrockModel(stripped);\n\n if (normalized !== modelName) candidates.push(normalized);\n if (stripped !== modelName && stripped !== normalized)\n candidates.push(stripped);\n candidates.push(modelName);\n\n return [...new Set(candidates)];\n}\n\nfunction safeInt(value: unknown): number {\n if (typeof value === 'number' && !Number.isNaN(value))\n return Math.round(value);\n return 0;\n}\n\nexport function calculateCost(options: {\n modelName: string;\n inputTokens: number;\n outputTokens: number;\n reasoningTokens?: number;\n cacheReadInputTokens?: number;\n cacheCreationInputTokens?: number;\n}): number {\n const {\n modelName,\n inputTokens,\n outputTokens,\n reasoningTokens = 0,\n cacheReadInputTokens = 0,\n cacheCreationInputTokens = 0,\n } = options;\n\n if (genaiPrices != null) {\n try {\n const prices = genaiPrices as Record<string, unknown>;\n if (typeof prices.calculateCost === 'function') {\n const stripped = stripProviderPrefix(modelName);\n const normalized = normalizeBedrockModel(stripped);\n const cost = prices.calculateCost({\n model: normalized,\n inputTokens: safeInt(inputTokens),\n outputTokens: safeInt(outputTokens),\n reasoningTokens: safeInt(reasoningTokens),\n cacheReadInputTokens: safeInt(cacheReadInputTokens),\n cacheCreationInputTokens: safeInt(cacheCreationInputTokens),\n }) as number | null;\n return cost ?? 0;\n }\n } catch {\n // Fall through to 0\n }\n }\n\n return 0;\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAM,cAAc,WAAW,yBAAyB;AAExD,SAAgB,oBAAoB,WAA2B;CAC7D,MAAM,WAAW,UAAU,QAAQ,IAAI;AACvC,QAAO,YAAY,IAAI,UAAU,MAAM,WAAW,EAAE,GAAG;;AAGzD,SAAS,sBAAsB,WAA2B;AAIxD,QAHc,UAAU,MACtB,oEACD,GACc,MAAM;;;;;;AAOvB,MAAa,gBAAgB;;;;;;AAO7B,SAAgB,8BAA8B,WAA6B;CACzE,MAAMA,aAAuB,EAAE;CAC/B,MAAM,WAAW,oBAAoB,UAAU;CAC/C,MAAM,aAAa,sBAAsB,SAAS;AAElD,KAAI,eAAe,UAAW,YAAW,KAAK,WAAW;AACzD,KAAI,aAAa,aAAa,aAAa,WACzC,YAAW,KAAK,SAAS;AAC3B,YAAW,KAAK,UAAU;AAE1B,QAAO,CAAC,GAAG,IAAI,IAAI,WAAW,CAAC;;AAGjC,SAAS,QAAQ,OAAwB;AACvC,KAAI,OAAO,UAAU,YAAY,CAAC,OAAO,MAAM,MAAM,CACnD,QAAO,KAAK,MAAM,MAAM;AAC1B,QAAO;;AAGT,SAAgB,cAAc,SAOnB;CACT,MAAM,EACJ,WACA,aACA,cACA,kBAAkB,GAClB,uBAAuB,GACvB,2BAA2B,MACzB;AAEJ,KAAI,eAAe,KACjB,KAAI;EACF,MAAM,SAAS;AACf,MAAI,OAAO,OAAO,kBAAkB,YAAY;GAE9C,MAAM,aAAa,sBADF,oBAAoB,UAAU,CACG;AASlD,UARa,OAAO,cAAc;IAChC,OAAO;IACP,aAAa,QAAQ,YAAY;IACjC,cAAc,QAAQ,aAAa;IACnC,iBAAiB,QAAQ,gBAAgB;IACzC,sBAAsB,QAAQ,qBAAqB;IACnD,0BAA0B,QAAQ,yBAAyB;IAC5D,CAAC,IACa;;SAEX;AAKV,QAAO"}
1
+ {"version":3,"file":"costs.js","names":["candidates: Array<{ model: string; providerId?: string }>","opts: Record<string, unknown>"],"sources":["../../src/utils/costs.ts"],"sourcesContent":["/**\n * Cost calculation utilities.\n *\n * Uses the genai-prices package when available (npm: @pydantic/genai-prices).\n * Falls back to returning 0 when not installed.\n */\n\nimport {\n inferProviderFromModel,\n tryInferProviderFromModel,\n} from './providers.js';\nimport { tryRequire } from './resolve-module.js';\n\nconst genaiPrices = tryRequire('@pydantic/genai-prices');\n\nlet _livePricesEnabled = false;\n\n/**\n * Opt in to background price updates from the genai-prices GitHub repo.\n *\n * Call once at application startup (e.g. after `AmplitudeAI` init) to fetch\n * the latest pricing data periodically. This ensures new model pricing is\n * available within days of being added to the genai-prices repository,\n * instead of waiting for an npm package release.\n *\n * This makes outbound HTTPS requests to raw.githubusercontent.com.\n * Only enable in environments where outbound network access is permitted.\n *\n * @param intervalMs - refresh interval in milliseconds (default: 1 hour)\n */\nexport function enableLivePriceUpdates(intervalMs = 3_600_000): void {\n if (_livePricesEnabled || genaiPrices == null) return;\n _livePricesEnabled = true;\n\n const prices = genaiPrices as Record<string, unknown>;\n if (typeof prices.updatePrices !== 'function') return;\n\n const doUpdate = () => {\n try {\n (prices.updatePrices as (cb: (ctx: {\n remoteDataUrl: string;\n setProviderData: (data: unknown) => void;\n }) => void) => void)(\n async ({ remoteDataUrl, setProviderData }) => {\n try {\n const resp = await fetch(remoteDataUrl);\n if (resp.ok) {\n setProviderData(await resp.json());\n }\n } catch {\n // Network errors are non-fatal — bundled data still works\n }\n },\n );\n } catch {\n // Best-effort\n }\n };\n\n doUpdate();\n setInterval(doUpdate, intervalMs).unref?.();\n}\n\nexport function stripProviderPrefix(modelName: string): string {\n const colonIdx = modelName.indexOf(':');\n return colonIdx >= 0 ? modelName.slice(colonIdx + 1) : modelName;\n}\n\n/**\n * Infer the provider name from a model name.\n * Delegates to the canonical implementation in utils/providers.ts.\n */\nexport const inferProvider = inferProviderFromModel;\n\nfunction normalizeProviderForGenaiPrices(\n provider: string | undefined,\n): string | undefined {\n if (provider === 'gemini') return 'google';\n return provider;\n}\n\n/**\n * Generate candidate (modelRef, providerId) pairs for price lookup.\n *\n * For Bedrock/AWS models, uses a **generalized** dot-prefix stripping strategy\n * instead of enumerating known regions or vendors. Bedrock model IDs follow\n * `[region.][vendor.]model-name[-version]` — we progressively strip\n * dot-separated prefixes and try each variant with and without provider,\n * plus `regional.` / `global.` prefixes that genai-prices uses.\n *\n * This approach is forward-compatible: new AWS regions and Bedrock vendors\n * work automatically without code changes.\n */\nexport function getGenaiPriceLookupCandidates(\n modelName: string,\n defaultProvider?: string,\n): Array<{ model: string; providerId?: string }> {\n const stripped = stripProviderPrefix(modelName);\n const inferred = defaultProvider ?? tryInferProviderFromModel(stripped);\n\n const isBedrock =\n inferred === 'bedrock' ||\n defaultProvider === 'bedrock' ||\n modelName.startsWith('bedrock:');\n const providerId = isBedrock\n ? 'aws'\n : normalizeProviderForGenaiPrices(inferred);\n\n const candidates: Array<{ model: string; providerId?: string }> = [\n { model: stripped, providerId },\n ];\n // For Bedrock, also try without provider for globally-matched models (e.g. Claude)\n if (isBedrock) {\n candidates.push({ model: stripped, providerId: undefined });\n }\n\n // For any model with dot-separated segments (e.g. vendor.model, region.vendor.model),\n // progressively strip prefixes. This is safe: iteration stops at the first price hit.\n // For Bedrock models specifically, also try regional./global. prefixes.\n if (stripped.includes('.')) {\n const parts = stripped.split('.');\n for (let i = 1; i < parts.length; i++) {\n const sub = parts.slice(i).join('.');\n candidates.push({ model: sub, providerId });\n candidates.push({ model: sub });\n }\n\n if (isBedrock) {\n // genai-prices often indexes Bedrock models under regional.X / global.X\n let vendorModel = stripped;\n const firstSeg = parts[0];\n if (\n firstSeg !== 'regional' &&\n firstSeg !== 'global' &&\n parts.length > 2\n ) {\n vendorModel = parts.slice(1).join('.');\n }\n if (\n !vendorModel.startsWith('regional.') &&\n !vendorModel.startsWith('global.')\n ) {\n candidates.push({ model: `regional.${vendorModel}` });\n candidates.push({ model: `global.${vendorModel}` });\n }\n }\n }\n\n // Deduplicate\n const seen = new Set<string>();\n return candidates.filter((c) => {\n const key = `${c.model}::${c.providerId ?? ''}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\nfunction safeInt(value: unknown): number {\n if (typeof value === 'number' && !Number.isNaN(value))\n return Math.round(value);\n return 0;\n}\n\n/**\n * Calculate cost for an LLM call using genai-prices.\n *\n * IMPORTANT CONTRACT:\n * - `inputTokens` MUST be the TOTAL input token count (including cached tokens).\n * For Anthropic: raw input_tokens + cache_read + cache_creation.\n * For OpenAI: prompt_tokens already includes cached_tokens.\n * - `outputTokens` MUST be the TOTAL output token count (including reasoning tokens).\n * For OpenAI: completion_tokens already includes reasoning_tokens.\n * Do NOT pass reasoning tokens separately and then add them here.\n * - `cacheReadInputTokens` and `cacheCreationInputTokens` are SUBSETS of inputTokens,\n * used only for differential pricing (cached tokens are cheaper).\n * - `reasoningTokens` is IGNORED for cost calculation — it exists only for backward\n * compatibility. Reasoning tokens are already included in outputTokens.\n */\nexport function calculateCost(options: {\n modelName: string;\n inputTokens: number;\n outputTokens: number;\n /** @deprecated Ignored — reasoning tokens are already included in outputTokens. */\n reasoningTokens?: number;\n cacheReadInputTokens?: number;\n cacheCreationInputTokens?: number;\n defaultProvider?: string;\n}): number {\n const {\n modelName,\n inputTokens,\n outputTokens,\n cacheReadInputTokens = 0,\n cacheCreationInputTokens = 0,\n defaultProvider,\n } = options;\n\n if (genaiPrices != null) {\n try {\n const prices = genaiPrices as Record<string, unknown>;\n if (typeof prices.calcPrice === 'function') {\n const calcPriceFn = prices.calcPrice as (\n usage: Record<string, number>,\n modelId: string,\n options?: Record<string, unknown>,\n ) => { total_price?: number } | null;\n\n const usage = {\n input_tokens: safeInt(inputTokens),\n output_tokens: safeInt(outputTokens),\n cache_read_tokens: safeInt(cacheReadInputTokens),\n cache_write_tokens: safeInt(cacheCreationInputTokens),\n };\n\n const candidates = getGenaiPriceLookupCandidates(\n modelName,\n defaultProvider,\n );\n for (const { model, providerId } of candidates) {\n const opts: Record<string, unknown> = {};\n if (providerId) opts.providerId = providerId;\n const result = calcPriceFn(\n usage,\n model,\n Object.keys(opts).length > 0 ? opts : undefined,\n );\n if (result?.total_price != null && result.total_price > 0) {\n return result.total_price;\n }\n }\n return 0;\n }\n } catch {\n // Fall through to 0\n }\n }\n\n return 0;\n}\n"],"mappings":"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"}
@@ -1,5 +1,10 @@
1
1
  //#region src/utils/providers.d.ts
2
+ /**
3
+ * Try to infer provider from model name patterns.
4
+ * Returns undefined if no pattern matches (unlike the public API which defaults to 'openai').
5
+ */
6
+ declare function tryInferProviderFromModel(modelName: string): string | undefined;
2
7
  declare function inferProviderFromModel(modelName: string): string;
3
8
  //#endregion
4
- export { inferProviderFromModel };
9
+ export { inferProviderFromModel, tryInferProviderFromModel };
5
10
  //# sourceMappingURL=providers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","names":[],"sources":["../../src/utils/providers.ts"],"sourcesContent":[],"mappings":";iBAYgB,sBAAA"}
1
+ {"version":3,"file":"providers.d.ts","names":[],"sources":["../../src/utils/providers.ts"],"sourcesContent":[],"mappings":";;AAgBA;AASA;;iBATgB,yBAAA;iBASA,sBAAA"}
@@ -10,11 +10,17 @@ const PROVIDER_PATTERNS = [
10
10
  [/^jamba|^ai21\./i, "ai21"],
11
11
  [/^deepseek/i, "deepseek"]
12
12
  ];
13
- function inferProviderFromModel(modelName) {
13
+ /**
14
+ * Try to infer provider from model name patterns.
15
+ * Returns undefined if no pattern matches (unlike the public API which defaults to 'openai').
16
+ */
17
+ function tryInferProviderFromModel(modelName) {
14
18
  for (const [pattern, provider] of PROVIDER_PATTERNS) if (pattern.test(modelName)) return provider;
15
- return "openai";
19
+ }
20
+ function inferProviderFromModel(modelName) {
21
+ return tryInferProviderFromModel(modelName) ?? "openai";
16
22
  }
17
23
 
18
24
  //#endregion
19
- export { inferProviderFromModel };
25
+ export { inferProviderFromModel, tryInferProviderFromModel };
20
26
  //# sourceMappingURL=providers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","names":["PROVIDER_PATTERNS: Array<[RegExp, string]>"],"sources":["../../src/utils/providers.ts"],"sourcesContent":["const PROVIDER_PATTERNS: Array<[RegExp, string]> = [\n [/^gpt-|^o[1-9]|^dall-e|^text-embedding|^whisper|^tts/i, 'openai'],\n [/^claude/i, 'anthropic'],\n [/^gemini|^palm/i, 'gemini'],\n [/^mistral|^codestral|^open-mistral|^pixtral/i, 'mistral'],\n [/^command|^embed-/i, 'cohere'],\n [/^amazon\\.|^titan/i, 'amazon'],\n [/^llama|^meta\\./i, 'meta'],\n [/^jamba|^ai21\\./i, 'ai21'],\n [/^deepseek/i, 'deepseek'],\n];\n\nexport function inferProviderFromModel(modelName: string): string {\n for (const [pattern, provider] of PROVIDER_PATTERNS) {\n if (pattern.test(modelName)) return provider;\n }\n return 'openai';\n}\n"],"mappings":";AAAA,MAAMA,oBAA6C;CACjD,CAAC,wDAAwD,SAAS;CAClE,CAAC,YAAY,YAAY;CACzB,CAAC,kBAAkB,SAAS;CAC5B,CAAC,+CAA+C,UAAU;CAC1D,CAAC,qBAAqB,SAAS;CAC/B,CAAC,qBAAqB,SAAS;CAC/B,CAAC,mBAAmB,OAAO;CAC3B,CAAC,mBAAmB,OAAO;CAC3B,CAAC,cAAc,WAAW;CAC3B;AAED,SAAgB,uBAAuB,WAA2B;AAChE,MAAK,MAAM,CAAC,SAAS,aAAa,kBAChC,KAAI,QAAQ,KAAK,UAAU,CAAE,QAAO;AAEtC,QAAO"}
1
+ {"version":3,"file":"providers.js","names":["PROVIDER_PATTERNS: Array<[RegExp, string]>"],"sources":["../../src/utils/providers.ts"],"sourcesContent":["const PROVIDER_PATTERNS: Array<[RegExp, string]> = [\n [/^gpt-|^o[1-9]|^dall-e|^text-embedding|^whisper|^tts/i, 'openai'],\n [/^claude/i, 'anthropic'],\n [/^gemini|^palm/i, 'gemini'],\n [/^mistral|^codestral|^open-mistral|^pixtral/i, 'mistral'],\n [/^command|^embed-/i, 'cohere'],\n [/^amazon\\.|^titan/i, 'amazon'],\n [/^llama|^meta\\./i, 'meta'],\n [/^jamba|^ai21\\./i, 'ai21'],\n [/^deepseek/i, 'deepseek'],\n];\n\n/**\n * Try to infer provider from model name patterns.\n * Returns undefined if no pattern matches (unlike the public API which defaults to 'openai').\n */\nexport function tryInferProviderFromModel(\n modelName: string,\n): string | undefined {\n for (const [pattern, provider] of PROVIDER_PATTERNS) {\n if (pattern.test(modelName)) return provider;\n }\n return undefined;\n}\n\nexport function inferProviderFromModel(modelName: string): string {\n return tryInferProviderFromModel(modelName) ?? 'openai';\n}\n"],"mappings":";AAAA,MAAMA,oBAA6C;CACjD,CAAC,wDAAwD,SAAS;CAClE,CAAC,YAAY,YAAY;CACzB,CAAC,kBAAkB,SAAS;CAC5B,CAAC,+CAA+C,UAAU;CAC1D,CAAC,qBAAqB,SAAS;CAC/B,CAAC,qBAAqB,SAAS;CAC/B,CAAC,mBAAmB,OAAO;CAC3B,CAAC,mBAAmB,OAAO;CAC3B,CAAC,cAAc,WAAW;CAC3B;;;;;AAMD,SAAgB,0BACd,WACoB;AACpB,MAAK,MAAM,CAAC,SAAS,aAAa,kBAChC,KAAI,QAAQ,KAAK,UAAU,CAAE,QAAO;;AAKxC,SAAgB,uBAAuB,WAA2B;AAChE,QAAO,0BAA0B,UAAU,IAAI"}
package/llms-full.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  # llms-full.txt
2
- # @amplitude/ai 0.1.2 — Detailed API Reference for LLM Agents
2
+ # @amplitude/ai 0.2.1 — Detailed API Reference for LLM Agents
3
3
  # Use this file for instrumentation guidance. See llms.txt for discovery.
4
4
 
5
5
  ## Core API
@@ -29,8 +29,10 @@ wrap(azureClient, ai) → AzureOpenAI wrapper
29
29
  Create a bound agent for user/session lineage.
30
30
  ```
31
31
  const agent = ai.agent('my-agent', { userId: 'u1' })
32
+ const child = agent.child('sub-agent')
32
33
  const session = agent.session({ sessionId: 's1' })
33
34
  await session.run(async (s) => { ... })
35
+ await s.runAs(child, async (cs) => { ... }) // delegate to child agent
34
36
  ```
35
37
 
36
38
  ### tool(fn, options) / tool(options)(fn)
@@ -103,6 +105,20 @@ import { createAmplitudeAIMiddleware } from '@amplitude/ai';
103
105
  app.use(createAmplitudeAIMiddleware());
104
106
  ```
105
107
 
108
+ ### 6. Multi-agent orchestration with session.runAs()
109
+ ```typescript
110
+ const orchestrator = ai.agent('orchestrator', { userId: 'u1' });
111
+ const researcher = orchestrator.child('researcher');
112
+ const session = orchestrator.session({ sessionId: 's1' });
113
+ await session.run(async (s) => {
114
+ // Provider calls inside runAs are automatically tagged with the child's agentId
115
+ const result = await s.runAs(researcher, async (cs) => {
116
+ return openai.chat.completions.create({ model: 'gpt-4o', messages: [...] });
117
+ });
118
+ });
119
+ // runAs shares sessionId, traceId, turn counter; does NOT emit Session End
120
+ ```
121
+
106
122
  ## MCP Tools
107
123
 
108
124
  - `get_event_schema(event_type?)` — Return event schema and property definitions
package/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- GENERATED FILE: do not edit manually -->
2
2
  # llms.txt
3
3
  package=@amplitude/ai
4
- version=0.1.2
4
+ version=0.2.1
5
5
 
6
6
  [mcp.tools]
7
7
  get_event_schema
package/mcp.schema.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "generated": true,
3
3
  "package": "@amplitude/ai",
4
- "version": "0.1.2",
4
+ "version": "0.2.1",
5
5
  "prompt": "instrument_app",
6
6
  "tools": [
7
7
  "get_event_schema",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amplitude/ai",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "Amplitude AI SDK - LLM usage tracking for Amplitude Analytics",
6
6
  "keywords": [