inngest 4.10.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/components/execution/otel/aiExtractor.cjs +1 -0
- package/components/execution/otel/aiExtractor.cjs.map +1 -1
- package/components/execution/otel/aiExtractor.d.cts +5 -0
- package/components/execution/otel/aiExtractor.d.ts +5 -0
- package/components/execution/otel/aiExtractor.js +1 -0
- package/components/execution/otel/aiExtractor.js.map +1 -1
- package/package.json +1 -1
- package/version.cjs +1 -1
- package/version.cjs.map +1 -1
- package/version.d.cts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# inngest
|
|
2
2
|
|
|
3
|
+
## 4.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1601](https://github.com/inngest/inngest-js/pull/1601) [`2b800c73`](https://github.com/inngest/inngest-js/commit/2b800c73cba6a2ff106517d2cd406628b7240c72) Thanks [@scottnuma](https://github.com/scottnuma)! - Support parsing operation name for AI Metadata Extraction
|
|
8
|
+
|
|
3
9
|
## 4.10.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -35,6 +35,7 @@ const FIELD_SPECS = [
|
|
|
35
35
|
textField("requestModel", "gen_ai.request.model"),
|
|
36
36
|
textField("responseModel", "gen_ai.response.model"),
|
|
37
37
|
textField("provider", "gen_ai.provider.name", ["gen_ai.system"]),
|
|
38
|
+
textField("operationName", "gen_ai.operation.name"),
|
|
38
39
|
textField("responseId", "gen_ai.response.id"),
|
|
39
40
|
stringListField("finishReasons", "gen_ai.response.finish_reasons"),
|
|
40
41
|
numberField("inputTokens", "gen_ai.usage.input_tokens", "sum"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiExtractor.cjs","names":["metadata: AIMetadata","out: AIMetadata","values: Record<string, unknown>"],"sources":["../../../../src/components/execution/otel/aiExtractor.ts"],"sourcesContent":["import type { Attributes } from \"@opentelemetry/api\";\n\n/**\n * Canonical AI metadata extracted from a span's OpenTelemetry GenAI semantic\n * convention (`gen_ai.*`) attributes.\n *\n * The fields below are an explicit **allowlist**: only these are ever read from\n * a span (see {@link FIELD_SPECS}). Anything not mapped — prompt/response\n * content, messages, tool calls, embeddings payloads, and other\n * potentially-sensitive or bulky attributes — is not captured.\n *\n * For each category we describe how multiple AIMetadata values are aggregated\n * (see {@link aggregate}).\n */\nexport interface AIMetadata {\n // Identity & classification (last-write-wins).\n\n /** The requested model, e.g. `gpt-4.1-nano`. */\n requestModel?: string;\n /**\n * The model that actually served the request. This is often a dated snapshot\n * of the requested {@link AIMetadata.requestModel} (e.g.\n * `gpt-4.1-nano-2025-04-14`).\n */\n responseModel?: string;\n /** The AI provider that served the request (e.g. `openai`) */\n provider?: string;\n /** The provider's identifier for the response, e.g. `chatcmpl-...`. */\n responseId?: string;\n /**\n * Why generation stopped, as reported by the provider, e.g. `[\"stop\"]`,\n * `[\"length\"]`, or `[\"tool_calls\"]`. Last-write-wins across calls.\n */\n finishReasons?: string[];\n\n // Token usage (summed).\n\n /** The number of input tokens consumed by the request. */\n inputTokens?: number;\n /** The number of output tokens produced by the response. */\n outputTokens?: number;\n /** The total tokens consumed, as reported by the provider. */\n totalTokens?: number;\n /**\n * Cached input tokens read from the prompt cache. Providers differ on\n * accounting. OpenAI's cached tokens are a subset of\n * {@link AIMetadata.inputTokens}, Anthropic's are additive. Callers must\n * not assume a single relationship.\n */\n cacheReadTokens?: number;\n /** Tokens written to the prompt cache. */\n cacheCreationTokens?: number;\n /** Reasoning/thinking tokens, when the emitter reports them separately. */\n reasoningTokens?: number;\n\n // Request / inference parameters (last-write-wins). These describe the\n // request the caller made, not the response, and are stored raw as the\n // emitter reports them.\n\n /** Sampling temperature requested, e.g. `0.7`. */\n temperature?: number;\n /** Nucleus sampling probability mass requested (`top_p`), e.g. `0.9`. */\n topP?: number;\n /** Upper bound on tokens to generate (`max_tokens`). */\n maxTokens?: number;\n /** Frequency penalty requested. */\n frequencyPenalty?: number;\n /** Presence penalty requested. */\n presencePenalty?: number;\n /** Sampling seed requested, when the emitter reports it. */\n seed?: number;\n}\n\ntype ValueType = \"text\" | \"number\" | \"stringList\";\ntype MergeStrategy = \"replace\" | \"sum\";\n\n/**\n * Derives the subset of AIMetadata keys whose assigned value matches TValue.\n *\n * AIMetadata fields are optional (`model?: string`), so each field's type is\n * effectively `string | undefined` or `number | undefined`. `Exclude` removes\n * `undefined` before checking the value type, and `-?` prevents optional fields\n * from adding `undefined` back into the final key union.\n */\ntype FieldsWithValue<TValue> = {\n [TField in keyof AIMetadata]-?: Exclude<\n AIMetadata[TField],\n undefined\n > extends TValue\n ? TField\n : never;\n}[keyof AIMetadata];\n\ntype TextField = FieldsWithValue<string>;\ntype NumberField = FieldsWithValue<number>;\ntype StringListField = FieldsWithValue<string[]>;\n\ninterface BaseFieldSpec {\n /** The canonical (preferred) source `gen_ai.*` attribute key. */\n source: string;\n /**\n * Deprecated source keys to read when {@link BaseFieldSpec.source} is absent\n * or carries no usable value, in descending precedence. The preferred\n * {@link BaseFieldSpec.source} always wins when it supplies a value; these are\n * only consulted as fallbacks.\n */\n fallbackSources?: readonly string[];\n valueType: ValueType;\n merge: MergeStrategy;\n}\n\ninterface TextFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: TextField;\n valueType: \"text\";\n merge: \"replace\";\n}\n\ninterface NumberFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: NumberField;\n valueType: \"number\";\n}\n\ninterface StringListFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: StringListField;\n valueType: \"stringList\";\n merge: \"replace\";\n}\n\ntype FieldSpec = TextFieldSpec | NumberFieldSpec | StringListFieldSpec;\n\nfunction textField(\n field: TextField,\n source: string,\n fallbackSources?: readonly string[],\n): TextFieldSpec {\n return {\n field,\n source,\n fallbackSources,\n valueType: \"text\",\n merge: \"replace\",\n };\n}\n\nfunction numberField(\n field: NumberField,\n source: string,\n merge: MergeStrategy,\n): NumberFieldSpec {\n return {\n field,\n source,\n valueType: \"number\",\n merge,\n };\n}\n\nfunction stringListField(\n field: StringListField,\n source: string,\n): StringListFieldSpec {\n return {\n field,\n source,\n valueType: \"stringList\",\n merge: \"replace\",\n };\n}\n\n/**\n * Every `gen_ai.*` attribute we capture, mapped to its canonical field.\n *\n * Anything not listed here (prompt/response content, messages, tool calls,\n * unknown keys) is ignored.\n */\nexport const FIELD_SPECS = [\n // Identity & classification.\n textField(\"requestModel\", \"gen_ai.request.model\"),\n textField(\"responseModel\", \"gen_ai.response.model\"),\n // `gen_ai.system` is the deprecated predecessor of `gen_ai.provider.name`;\n // the canonical key wins when both are present on a span.\n textField(\"provider\", \"gen_ai.provider.name\", [\"gen_ai.system\"]),\n textField(\"responseId\", \"gen_ai.response.id\"),\n stringListField(\"finishReasons\", \"gen_ai.response.finish_reasons\"),\n\n // Token usage.\n numberField(\"inputTokens\", \"gen_ai.usage.input_tokens\", \"sum\"),\n numberField(\"outputTokens\", \"gen_ai.usage.output_tokens\", \"sum\"),\n numberField(\"totalTokens\", \"gen_ai.usage.total_tokens\", \"sum\"),\n numberField(\"cacheReadTokens\", \"gen_ai.usage.cache_read.input_tokens\", \"sum\"),\n numberField(\n \"cacheCreationTokens\",\n \"gen_ai.usage.cache_creation.input_tokens\",\n \"sum\",\n ),\n numberField(\"reasoningTokens\", \"gen_ai.usage.reasoning.output_tokens\", \"sum\"),\n\n // Request / inference parameters. These describe the request, not a quantity,\n // so they replace rather than sum when a step makes several calls.\n numberField(\"temperature\", \"gen_ai.request.temperature\", \"replace\"),\n numberField(\"topP\", \"gen_ai.request.top_p\", \"replace\"),\n numberField(\"maxTokens\", \"gen_ai.request.max_tokens\", \"replace\"),\n numberField(\n \"frequencyPenalty\",\n \"gen_ai.request.frequency_penalty\",\n \"replace\",\n ),\n numberField(\"presencePenalty\", \"gen_ai.request.presence_penalty\", \"replace\"),\n numberField(\"seed\", \"gen_ai.request.seed\", \"replace\"),\n] as const satisfies readonly FieldSpec[];\n\nfunction parseNumericAttribute(value: unknown): number | undefined {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : undefined;\n }\n\n if (typeof value !== \"string\" || value === \"\") {\n return undefined;\n }\n\n const num = Number(value);\n return Number.isFinite(num) ? num : undefined;\n}\n\n/**\n * Coerces an attribute to a list of non-empty strings, or `undefined` when it\n * yields none. OTLP arrays may carry non-string or empty entries; those are\n * dropped, and an array that reduces to nothing is treated as absent.\n */\nfunction parseStringListAttribute(value: unknown): string[] | undefined {\n const items = Array.isArray(value) ? value : [value];\n const strings = items.filter(\n (item): item is string => typeof item === \"string\" && item !== \"\",\n );\n return strings.length > 0 ? strings : undefined;\n}\n\n/**\n * Extracts {@link AIMetadata} from a span's attributes.\n *\n * Only the allowlisted {@link FIELD_SPECS} are read. Every other attribute is\n * ignored. Numeric fields accept numbers and quoted numeric strings and are\n * otherwise dropped, as OTLP/JSON may encode int64 as a quoted string. Text\n * fields are dropped when empty. String-list fields keep only non-empty string\n * entries and are dropped when none remain.\n *\n * Returns an empty object for spans carrying no recognised `gen_ai.*`\n * attributes.\n *\n * @param attributes - The span attributes, as exposed by\n * `ReadableSpan.attributes`.\n */\nexport const extractAIMetadataFromAttributes = (\n attributes: Attributes,\n): AIMetadata => {\n const metadata: AIMetadata = {};\n\n for (const spec of FIELD_SPECS) {\n // Read the canonical key first, then any deprecated fallbacks in order,\n // taking the first that yields a usable value.\n const sources = [spec.source, ...(spec.fallbackSources ?? [])];\n\n for (const source of sources) {\n const value = attributes[source];\n if (value === undefined) {\n continue;\n }\n\n if (spec.valueType === \"text\") {\n const text = typeof value === \"string\" ? value : String(value);\n if (text !== \"\") {\n metadata[spec.field] = text;\n break;\n }\n } else if (spec.valueType === \"stringList\") {\n const list = parseStringListAttribute(value);\n if (list !== undefined) {\n metadata[spec.field] = list;\n break;\n }\n } else {\n const num = parseNumericAttribute(value);\n if (num !== undefined) {\n metadata[spec.field] = num;\n break;\n }\n }\n }\n }\n\n return metadata;\n};\n\n/**\n * Aggregates two {@link AIMetadata} values, folding a later call's metadata into\n * an earlier accumulator for a single step.\n *\n * - Token-count fields (`merge: \"sum\"`) are **summed**, so a step that makes\n * several LLM calls reports their combined usage.\n * - Every other field uses `merge: \"replace\"` (`b` overwrites `a`)\n *\n * A field is present in the result only when at least one input supplies it.\n *\n * @param a - The accumulator (earlier calls).\n * @param b - The later metadata, whose values win on conflict (and add for sums).\n */\nexport const aggregate = (a: AIMetadata, b: AIMetadata): AIMetadata => {\n const out: AIMetadata = { ...a };\n\n for (const spec of FIELD_SPECS) {\n if (spec.valueType === \"number\") {\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n\n if (spec.merge === \"sum\") {\n out[spec.field] = (out[spec.field] ?? 0) + bValue;\n } else {\n out[spec.field] = bValue;\n }\n } else if (spec.valueType === \"text\") {\n // Text fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n } else {\n // String-list fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n }\n }\n\n return out;\n};\n\n/** Converts a canonical camelCase field name to the server's snake_case key. */\nconst toSnakeCase = (field: string): string =>\n field.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);\n\n/**\n * Maps an {@link AIMetadata} value onto the server's `inngest.ai` metadata\n * values, converting each canonical camelCase field to snake_case (e.g.\n * `inputTokens` → `input_tokens`). Returns `undefined` when there is nothing to\n * emit so callers can skip stamping metadata entirely.\n */\nexport const toInngestAIMetadataValues = (\n metadata: AIMetadata,\n): Record<string, unknown> | undefined => {\n const entries = Object.entries(metadata);\n if (entries.length === 0) {\n return undefined;\n }\n\n const values: Record<string, unknown> = {};\n for (const [field, value] of entries) {\n values[toSnakeCase(field)] = value;\n }\n\n return values;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aiExtractor.cjs","names":["metadata: AIMetadata","out: AIMetadata","values: Record<string, unknown>"],"sources":["../../../../src/components/execution/otel/aiExtractor.ts"],"sourcesContent":["import type { Attributes } from \"@opentelemetry/api\";\n\n/**\n * Canonical AI metadata extracted from a span's OpenTelemetry GenAI semantic\n * convention (`gen_ai.*`) attributes.\n *\n * The fields below are an explicit **allowlist**: only these are ever read from\n * a span (see {@link FIELD_SPECS}). Anything not mapped — prompt/response\n * content, messages, tool calls, embeddings payloads, and other\n * potentially-sensitive or bulky attributes — is not captured.\n *\n * For each category we describe how multiple AIMetadata values are aggregated\n * (see {@link aggregate}).\n */\nexport interface AIMetadata {\n // Identity & classification (last-write-wins).\n\n /** The requested model, e.g. `gpt-4.1-nano`. */\n requestModel?: string;\n /**\n * The model that actually served the request. This is often a dated snapshot\n * of the requested {@link AIMetadata.requestModel} (e.g.\n * `gpt-4.1-nano-2025-04-14`).\n */\n responseModel?: string;\n /** The AI provider that served the request (e.g. `openai`) */\n provider?: string;\n /**\n * The kind of operation performed, e.g. `chat`, `text_completion`,\n * `embeddings`. Last-write-wins across calls.\n */\n operationName?: string;\n /** The provider's identifier for the response, e.g. `chatcmpl-...`. */\n responseId?: string;\n /**\n * Why generation stopped, as reported by the provider, e.g. `[\"stop\"]`,\n * `[\"length\"]`, or `[\"tool_calls\"]`. Last-write-wins across calls.\n */\n finishReasons?: string[];\n\n // Token usage (summed).\n\n /** The number of input tokens consumed by the request. */\n inputTokens?: number;\n /** The number of output tokens produced by the response. */\n outputTokens?: number;\n /** The total tokens consumed, as reported by the provider. */\n totalTokens?: number;\n /**\n * Cached input tokens read from the prompt cache. Providers differ on\n * accounting. OpenAI's cached tokens are a subset of\n * {@link AIMetadata.inputTokens}, Anthropic's are additive. Callers must\n * not assume a single relationship.\n */\n cacheReadTokens?: number;\n /** Tokens written to the prompt cache. */\n cacheCreationTokens?: number;\n /** Reasoning/thinking tokens, when the emitter reports them separately. */\n reasoningTokens?: number;\n\n // Request / inference parameters (last-write-wins). These describe the\n // request the caller made, not the response, and are stored raw as the\n // emitter reports them.\n\n /** Sampling temperature requested, e.g. `0.7`. */\n temperature?: number;\n /** Nucleus sampling probability mass requested (`top_p`), e.g. `0.9`. */\n topP?: number;\n /** Upper bound on tokens to generate (`max_tokens`). */\n maxTokens?: number;\n /** Frequency penalty requested. */\n frequencyPenalty?: number;\n /** Presence penalty requested. */\n presencePenalty?: number;\n /** Sampling seed requested, when the emitter reports it. */\n seed?: number;\n}\n\ntype ValueType = \"text\" | \"number\" | \"stringList\";\ntype MergeStrategy = \"replace\" | \"sum\";\n\n/**\n * Derives the subset of AIMetadata keys whose assigned value matches TValue.\n *\n * AIMetadata fields are optional (`model?: string`), so each field's type is\n * effectively `string | undefined` or `number | undefined`. `Exclude` removes\n * `undefined` before checking the value type, and `-?` prevents optional fields\n * from adding `undefined` back into the final key union.\n */\ntype FieldsWithValue<TValue> = {\n [TField in keyof AIMetadata]-?: Exclude<\n AIMetadata[TField],\n undefined\n > extends TValue\n ? TField\n : never;\n}[keyof AIMetadata];\n\ntype TextField = FieldsWithValue<string>;\ntype NumberField = FieldsWithValue<number>;\ntype StringListField = FieldsWithValue<string[]>;\n\ninterface BaseFieldSpec {\n /** The canonical (preferred) source `gen_ai.*` attribute key. */\n source: string;\n /**\n * Deprecated source keys to read when {@link BaseFieldSpec.source} is absent\n * or carries no usable value, in descending precedence. The preferred\n * {@link BaseFieldSpec.source} always wins when it supplies a value; these are\n * only consulted as fallbacks.\n */\n fallbackSources?: readonly string[];\n valueType: ValueType;\n merge: MergeStrategy;\n}\n\ninterface TextFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: TextField;\n valueType: \"text\";\n merge: \"replace\";\n}\n\ninterface NumberFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: NumberField;\n valueType: \"number\";\n}\n\ninterface StringListFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: StringListField;\n valueType: \"stringList\";\n merge: \"replace\";\n}\n\ntype FieldSpec = TextFieldSpec | NumberFieldSpec | StringListFieldSpec;\n\nfunction textField(\n field: TextField,\n source: string,\n fallbackSources?: readonly string[],\n): TextFieldSpec {\n return {\n field,\n source,\n fallbackSources,\n valueType: \"text\",\n merge: \"replace\",\n };\n}\n\nfunction numberField(\n field: NumberField,\n source: string,\n merge: MergeStrategy,\n): NumberFieldSpec {\n return {\n field,\n source,\n valueType: \"number\",\n merge,\n };\n}\n\nfunction stringListField(\n field: StringListField,\n source: string,\n): StringListFieldSpec {\n return {\n field,\n source,\n valueType: \"stringList\",\n merge: \"replace\",\n };\n}\n\n/**\n * Every `gen_ai.*` attribute we capture, mapped to its canonical field.\n *\n * Anything not listed here (prompt/response content, messages, tool calls,\n * unknown keys) is ignored.\n */\nexport const FIELD_SPECS = [\n // Identity & classification.\n textField(\"requestModel\", \"gen_ai.request.model\"),\n textField(\"responseModel\", \"gen_ai.response.model\"),\n // `gen_ai.system` is the deprecated predecessor of `gen_ai.provider.name`;\n // the canonical key wins when both are present on a span.\n textField(\"provider\", \"gen_ai.provider.name\", [\"gen_ai.system\"]),\n textField(\"operationName\", \"gen_ai.operation.name\"),\n textField(\"responseId\", \"gen_ai.response.id\"),\n stringListField(\"finishReasons\", \"gen_ai.response.finish_reasons\"),\n\n // Token usage.\n numberField(\"inputTokens\", \"gen_ai.usage.input_tokens\", \"sum\"),\n numberField(\"outputTokens\", \"gen_ai.usage.output_tokens\", \"sum\"),\n numberField(\"totalTokens\", \"gen_ai.usage.total_tokens\", \"sum\"),\n numberField(\"cacheReadTokens\", \"gen_ai.usage.cache_read.input_tokens\", \"sum\"),\n numberField(\n \"cacheCreationTokens\",\n \"gen_ai.usage.cache_creation.input_tokens\",\n \"sum\",\n ),\n numberField(\"reasoningTokens\", \"gen_ai.usage.reasoning.output_tokens\", \"sum\"),\n\n // Request / inference parameters. These describe the request, not a quantity,\n // so they replace rather than sum when a step makes several calls.\n numberField(\"temperature\", \"gen_ai.request.temperature\", \"replace\"),\n numberField(\"topP\", \"gen_ai.request.top_p\", \"replace\"),\n numberField(\"maxTokens\", \"gen_ai.request.max_tokens\", \"replace\"),\n numberField(\n \"frequencyPenalty\",\n \"gen_ai.request.frequency_penalty\",\n \"replace\",\n ),\n numberField(\"presencePenalty\", \"gen_ai.request.presence_penalty\", \"replace\"),\n numberField(\"seed\", \"gen_ai.request.seed\", \"replace\"),\n] as const satisfies readonly FieldSpec[];\n\nfunction parseNumericAttribute(value: unknown): number | undefined {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : undefined;\n }\n\n if (typeof value !== \"string\" || value === \"\") {\n return undefined;\n }\n\n const num = Number(value);\n return Number.isFinite(num) ? num : undefined;\n}\n\n/**\n * Coerces an attribute to a list of non-empty strings, or `undefined` when it\n * yields none. OTLP arrays may carry non-string or empty entries; those are\n * dropped, and an array that reduces to nothing is treated as absent.\n */\nfunction parseStringListAttribute(value: unknown): string[] | undefined {\n const items = Array.isArray(value) ? value : [value];\n const strings = items.filter(\n (item): item is string => typeof item === \"string\" && item !== \"\",\n );\n return strings.length > 0 ? strings : undefined;\n}\n\n/**\n * Extracts {@link AIMetadata} from a span's attributes.\n *\n * Only the allowlisted {@link FIELD_SPECS} are read. Every other attribute is\n * ignored. Numeric fields accept numbers and quoted numeric strings and are\n * otherwise dropped, as OTLP/JSON may encode int64 as a quoted string. Text\n * fields are dropped when empty. String-list fields keep only non-empty string\n * entries and are dropped when none remain.\n *\n * Returns an empty object for spans carrying no recognised `gen_ai.*`\n * attributes.\n *\n * @param attributes - The span attributes, as exposed by\n * `ReadableSpan.attributes`.\n */\nexport const extractAIMetadataFromAttributes = (\n attributes: Attributes,\n): AIMetadata => {\n const metadata: AIMetadata = {};\n\n for (const spec of FIELD_SPECS) {\n // Read the canonical key first, then any deprecated fallbacks in order,\n // taking the first that yields a usable value.\n const sources = [spec.source, ...(spec.fallbackSources ?? [])];\n\n for (const source of sources) {\n const value = attributes[source];\n if (value === undefined) {\n continue;\n }\n\n if (spec.valueType === \"text\") {\n const text = typeof value === \"string\" ? value : String(value);\n if (text !== \"\") {\n metadata[spec.field] = text;\n break;\n }\n } else if (spec.valueType === \"stringList\") {\n const list = parseStringListAttribute(value);\n if (list !== undefined) {\n metadata[spec.field] = list;\n break;\n }\n } else {\n const num = parseNumericAttribute(value);\n if (num !== undefined) {\n metadata[spec.field] = num;\n break;\n }\n }\n }\n }\n\n return metadata;\n};\n\n/**\n * Aggregates two {@link AIMetadata} values, folding a later call's metadata into\n * an earlier accumulator for a single step.\n *\n * - Token-count fields (`merge: \"sum\"`) are **summed**, so a step that makes\n * several LLM calls reports their combined usage.\n * - Every other field uses `merge: \"replace\"` (`b` overwrites `a`)\n *\n * A field is present in the result only when at least one input supplies it.\n *\n * @param a - The accumulator (earlier calls).\n * @param b - The later metadata, whose values win on conflict (and add for sums).\n */\nexport const aggregate = (a: AIMetadata, b: AIMetadata): AIMetadata => {\n const out: AIMetadata = { ...a };\n\n for (const spec of FIELD_SPECS) {\n if (spec.valueType === \"number\") {\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n\n if (spec.merge === \"sum\") {\n out[spec.field] = (out[spec.field] ?? 0) + bValue;\n } else {\n out[spec.field] = bValue;\n }\n } else if (spec.valueType === \"text\") {\n // Text fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n } else {\n // String-list fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n }\n }\n\n return out;\n};\n\n/** Converts a canonical camelCase field name to the server's snake_case key. */\nconst toSnakeCase = (field: string): string =>\n field.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);\n\n/**\n * Maps an {@link AIMetadata} value onto the server's `inngest.ai` metadata\n * values, converting each canonical camelCase field to snake_case (e.g.\n * `inputTokens` → `input_tokens`). Returns `undefined` when there is nothing to\n * emit so callers can skip stamping metadata entirely.\n */\nexport const toInngestAIMetadataValues = (\n metadata: AIMetadata,\n): Record<string, unknown> | undefined => {\n const entries = Object.entries(metadata);\n if (entries.length === 0) {\n return undefined;\n }\n\n const values: Record<string, unknown> = {};\n for (const [field, value] of entries) {\n values[toSnakeCase(field)] = value;\n }\n\n return values;\n};\n"],"mappings":"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"}
|
|
@@ -22,6 +22,11 @@ interface AIMetadata {
|
|
|
22
22
|
responseModel?: string;
|
|
23
23
|
/** The AI provider that served the request (e.g. `openai`) */
|
|
24
24
|
provider?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The kind of operation performed, e.g. `chat`, `text_completion`,
|
|
27
|
+
* `embeddings`. Last-write-wins across calls.
|
|
28
|
+
*/
|
|
29
|
+
operationName?: string;
|
|
25
30
|
/** The provider's identifier for the response, e.g. `chatcmpl-...`. */
|
|
26
31
|
responseId?: string;
|
|
27
32
|
/**
|
|
@@ -25,6 +25,11 @@ interface AIMetadata {
|
|
|
25
25
|
responseModel?: string;
|
|
26
26
|
/** The AI provider that served the request (e.g. `openai`) */
|
|
27
27
|
provider?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The kind of operation performed, e.g. `chat`, `text_completion`,
|
|
30
|
+
* `embeddings`. Last-write-wins across calls.
|
|
31
|
+
*/
|
|
32
|
+
operationName?: string;
|
|
28
33
|
/** The provider's identifier for the response, e.g. `chatcmpl-...`. */
|
|
29
34
|
responseId?: string;
|
|
30
35
|
/**
|
|
@@ -34,6 +34,7 @@ const FIELD_SPECS = [
|
|
|
34
34
|
textField("requestModel", "gen_ai.request.model"),
|
|
35
35
|
textField("responseModel", "gen_ai.response.model"),
|
|
36
36
|
textField("provider", "gen_ai.provider.name", ["gen_ai.system"]),
|
|
37
|
+
textField("operationName", "gen_ai.operation.name"),
|
|
37
38
|
textField("responseId", "gen_ai.response.id"),
|
|
38
39
|
stringListField("finishReasons", "gen_ai.response.finish_reasons"),
|
|
39
40
|
numberField("inputTokens", "gen_ai.usage.input_tokens", "sum"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiExtractor.js","names":["metadata: AIMetadata","out: AIMetadata","values: Record<string, unknown>"],"sources":["../../../../src/components/execution/otel/aiExtractor.ts"],"sourcesContent":["import type { Attributes } from \"@opentelemetry/api\";\n\n/**\n * Canonical AI metadata extracted from a span's OpenTelemetry GenAI semantic\n * convention (`gen_ai.*`) attributes.\n *\n * The fields below are an explicit **allowlist**: only these are ever read from\n * a span (see {@link FIELD_SPECS}). Anything not mapped — prompt/response\n * content, messages, tool calls, embeddings payloads, and other\n * potentially-sensitive or bulky attributes — is not captured.\n *\n * For each category we describe how multiple AIMetadata values are aggregated\n * (see {@link aggregate}).\n */\nexport interface AIMetadata {\n // Identity & classification (last-write-wins).\n\n /** The requested model, e.g. `gpt-4.1-nano`. */\n requestModel?: string;\n /**\n * The model that actually served the request. This is often a dated snapshot\n * of the requested {@link AIMetadata.requestModel} (e.g.\n * `gpt-4.1-nano-2025-04-14`).\n */\n responseModel?: string;\n /** The AI provider that served the request (e.g. `openai`) */\n provider?: string;\n /** The provider's identifier for the response, e.g. `chatcmpl-...`. */\n responseId?: string;\n /**\n * Why generation stopped, as reported by the provider, e.g. `[\"stop\"]`,\n * `[\"length\"]`, or `[\"tool_calls\"]`. Last-write-wins across calls.\n */\n finishReasons?: string[];\n\n // Token usage (summed).\n\n /** The number of input tokens consumed by the request. */\n inputTokens?: number;\n /** The number of output tokens produced by the response. */\n outputTokens?: number;\n /** The total tokens consumed, as reported by the provider. */\n totalTokens?: number;\n /**\n * Cached input tokens read from the prompt cache. Providers differ on\n * accounting. OpenAI's cached tokens are a subset of\n * {@link AIMetadata.inputTokens}, Anthropic's are additive. Callers must\n * not assume a single relationship.\n */\n cacheReadTokens?: number;\n /** Tokens written to the prompt cache. */\n cacheCreationTokens?: number;\n /** Reasoning/thinking tokens, when the emitter reports them separately. */\n reasoningTokens?: number;\n\n // Request / inference parameters (last-write-wins). These describe the\n // request the caller made, not the response, and are stored raw as the\n // emitter reports them.\n\n /** Sampling temperature requested, e.g. `0.7`. */\n temperature?: number;\n /** Nucleus sampling probability mass requested (`top_p`), e.g. `0.9`. */\n topP?: number;\n /** Upper bound on tokens to generate (`max_tokens`). */\n maxTokens?: number;\n /** Frequency penalty requested. */\n frequencyPenalty?: number;\n /** Presence penalty requested. */\n presencePenalty?: number;\n /** Sampling seed requested, when the emitter reports it. */\n seed?: number;\n}\n\ntype ValueType = \"text\" | \"number\" | \"stringList\";\ntype MergeStrategy = \"replace\" | \"sum\";\n\n/**\n * Derives the subset of AIMetadata keys whose assigned value matches TValue.\n *\n * AIMetadata fields are optional (`model?: string`), so each field's type is\n * effectively `string | undefined` or `number | undefined`. `Exclude` removes\n * `undefined` before checking the value type, and `-?` prevents optional fields\n * from adding `undefined` back into the final key union.\n */\ntype FieldsWithValue<TValue> = {\n [TField in keyof AIMetadata]-?: Exclude<\n AIMetadata[TField],\n undefined\n > extends TValue\n ? TField\n : never;\n}[keyof AIMetadata];\n\ntype TextField = FieldsWithValue<string>;\ntype NumberField = FieldsWithValue<number>;\ntype StringListField = FieldsWithValue<string[]>;\n\ninterface BaseFieldSpec {\n /** The canonical (preferred) source `gen_ai.*` attribute key. */\n source: string;\n /**\n * Deprecated source keys to read when {@link BaseFieldSpec.source} is absent\n * or carries no usable value, in descending precedence. The preferred\n * {@link BaseFieldSpec.source} always wins when it supplies a value; these are\n * only consulted as fallbacks.\n */\n fallbackSources?: readonly string[];\n valueType: ValueType;\n merge: MergeStrategy;\n}\n\ninterface TextFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: TextField;\n valueType: \"text\";\n merge: \"replace\";\n}\n\ninterface NumberFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: NumberField;\n valueType: \"number\";\n}\n\ninterface StringListFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: StringListField;\n valueType: \"stringList\";\n merge: \"replace\";\n}\n\ntype FieldSpec = TextFieldSpec | NumberFieldSpec | StringListFieldSpec;\n\nfunction textField(\n field: TextField,\n source: string,\n fallbackSources?: readonly string[],\n): TextFieldSpec {\n return {\n field,\n source,\n fallbackSources,\n valueType: \"text\",\n merge: \"replace\",\n };\n}\n\nfunction numberField(\n field: NumberField,\n source: string,\n merge: MergeStrategy,\n): NumberFieldSpec {\n return {\n field,\n source,\n valueType: \"number\",\n merge,\n };\n}\n\nfunction stringListField(\n field: StringListField,\n source: string,\n): StringListFieldSpec {\n return {\n field,\n source,\n valueType: \"stringList\",\n merge: \"replace\",\n };\n}\n\n/**\n * Every `gen_ai.*` attribute we capture, mapped to its canonical field.\n *\n * Anything not listed here (prompt/response content, messages, tool calls,\n * unknown keys) is ignored.\n */\nexport const FIELD_SPECS = [\n // Identity & classification.\n textField(\"requestModel\", \"gen_ai.request.model\"),\n textField(\"responseModel\", \"gen_ai.response.model\"),\n // `gen_ai.system` is the deprecated predecessor of `gen_ai.provider.name`;\n // the canonical key wins when both are present on a span.\n textField(\"provider\", \"gen_ai.provider.name\", [\"gen_ai.system\"]),\n textField(\"responseId\", \"gen_ai.response.id\"),\n stringListField(\"finishReasons\", \"gen_ai.response.finish_reasons\"),\n\n // Token usage.\n numberField(\"inputTokens\", \"gen_ai.usage.input_tokens\", \"sum\"),\n numberField(\"outputTokens\", \"gen_ai.usage.output_tokens\", \"sum\"),\n numberField(\"totalTokens\", \"gen_ai.usage.total_tokens\", \"sum\"),\n numberField(\"cacheReadTokens\", \"gen_ai.usage.cache_read.input_tokens\", \"sum\"),\n numberField(\n \"cacheCreationTokens\",\n \"gen_ai.usage.cache_creation.input_tokens\",\n \"sum\",\n ),\n numberField(\"reasoningTokens\", \"gen_ai.usage.reasoning.output_tokens\", \"sum\"),\n\n // Request / inference parameters. These describe the request, not a quantity,\n // so they replace rather than sum when a step makes several calls.\n numberField(\"temperature\", \"gen_ai.request.temperature\", \"replace\"),\n numberField(\"topP\", \"gen_ai.request.top_p\", \"replace\"),\n numberField(\"maxTokens\", \"gen_ai.request.max_tokens\", \"replace\"),\n numberField(\n \"frequencyPenalty\",\n \"gen_ai.request.frequency_penalty\",\n \"replace\",\n ),\n numberField(\"presencePenalty\", \"gen_ai.request.presence_penalty\", \"replace\"),\n numberField(\"seed\", \"gen_ai.request.seed\", \"replace\"),\n] as const satisfies readonly FieldSpec[];\n\nfunction parseNumericAttribute(value: unknown): number | undefined {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : undefined;\n }\n\n if (typeof value !== \"string\" || value === \"\") {\n return undefined;\n }\n\n const num = Number(value);\n return Number.isFinite(num) ? num : undefined;\n}\n\n/**\n * Coerces an attribute to a list of non-empty strings, or `undefined` when it\n * yields none. OTLP arrays may carry non-string or empty entries; those are\n * dropped, and an array that reduces to nothing is treated as absent.\n */\nfunction parseStringListAttribute(value: unknown): string[] | undefined {\n const items = Array.isArray(value) ? value : [value];\n const strings = items.filter(\n (item): item is string => typeof item === \"string\" && item !== \"\",\n );\n return strings.length > 0 ? strings : undefined;\n}\n\n/**\n * Extracts {@link AIMetadata} from a span's attributes.\n *\n * Only the allowlisted {@link FIELD_SPECS} are read. Every other attribute is\n * ignored. Numeric fields accept numbers and quoted numeric strings and are\n * otherwise dropped, as OTLP/JSON may encode int64 as a quoted string. Text\n * fields are dropped when empty. String-list fields keep only non-empty string\n * entries and are dropped when none remain.\n *\n * Returns an empty object for spans carrying no recognised `gen_ai.*`\n * attributes.\n *\n * @param attributes - The span attributes, as exposed by\n * `ReadableSpan.attributes`.\n */\nexport const extractAIMetadataFromAttributes = (\n attributes: Attributes,\n): AIMetadata => {\n const metadata: AIMetadata = {};\n\n for (const spec of FIELD_SPECS) {\n // Read the canonical key first, then any deprecated fallbacks in order,\n // taking the first that yields a usable value.\n const sources = [spec.source, ...(spec.fallbackSources ?? [])];\n\n for (const source of sources) {\n const value = attributes[source];\n if (value === undefined) {\n continue;\n }\n\n if (spec.valueType === \"text\") {\n const text = typeof value === \"string\" ? value : String(value);\n if (text !== \"\") {\n metadata[spec.field] = text;\n break;\n }\n } else if (spec.valueType === \"stringList\") {\n const list = parseStringListAttribute(value);\n if (list !== undefined) {\n metadata[spec.field] = list;\n break;\n }\n } else {\n const num = parseNumericAttribute(value);\n if (num !== undefined) {\n metadata[spec.field] = num;\n break;\n }\n }\n }\n }\n\n return metadata;\n};\n\n/**\n * Aggregates two {@link AIMetadata} values, folding a later call's metadata into\n * an earlier accumulator for a single step.\n *\n * - Token-count fields (`merge: \"sum\"`) are **summed**, so a step that makes\n * several LLM calls reports their combined usage.\n * - Every other field uses `merge: \"replace\"` (`b` overwrites `a`)\n *\n * A field is present in the result only when at least one input supplies it.\n *\n * @param a - The accumulator (earlier calls).\n * @param b - The later metadata, whose values win on conflict (and add for sums).\n */\nexport const aggregate = (a: AIMetadata, b: AIMetadata): AIMetadata => {\n const out: AIMetadata = { ...a };\n\n for (const spec of FIELD_SPECS) {\n if (spec.valueType === \"number\") {\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n\n if (spec.merge === \"sum\") {\n out[spec.field] = (out[spec.field] ?? 0) + bValue;\n } else {\n out[spec.field] = bValue;\n }\n } else if (spec.valueType === \"text\") {\n // Text fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n } else {\n // String-list fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n }\n }\n\n return out;\n};\n\n/** Converts a canonical camelCase field name to the server's snake_case key. */\nconst toSnakeCase = (field: string): string =>\n field.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);\n\n/**\n * Maps an {@link AIMetadata} value onto the server's `inngest.ai` metadata\n * values, converting each canonical camelCase field to snake_case (e.g.\n * `inputTokens` → `input_tokens`). Returns `undefined` when there is nothing to\n * emit so callers can skip stamping metadata entirely.\n */\nexport const toInngestAIMetadataValues = (\n metadata: AIMetadata,\n): Record<string, unknown> | undefined => {\n const entries = Object.entries(metadata);\n if (entries.length === 0) {\n return undefined;\n }\n\n const values: Record<string, unknown> = {};\n for (const [field, value] of entries) {\n values[toSnakeCase(field)] = value;\n }\n\n return values;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aiExtractor.js","names":["metadata: AIMetadata","out: AIMetadata","values: Record<string, unknown>"],"sources":["../../../../src/components/execution/otel/aiExtractor.ts"],"sourcesContent":["import type { Attributes } from \"@opentelemetry/api\";\n\n/**\n * Canonical AI metadata extracted from a span's OpenTelemetry GenAI semantic\n * convention (`gen_ai.*`) attributes.\n *\n * The fields below are an explicit **allowlist**: only these are ever read from\n * a span (see {@link FIELD_SPECS}). Anything not mapped — prompt/response\n * content, messages, tool calls, embeddings payloads, and other\n * potentially-sensitive or bulky attributes — is not captured.\n *\n * For each category we describe how multiple AIMetadata values are aggregated\n * (see {@link aggregate}).\n */\nexport interface AIMetadata {\n // Identity & classification (last-write-wins).\n\n /** The requested model, e.g. `gpt-4.1-nano`. */\n requestModel?: string;\n /**\n * The model that actually served the request. This is often a dated snapshot\n * of the requested {@link AIMetadata.requestModel} (e.g.\n * `gpt-4.1-nano-2025-04-14`).\n */\n responseModel?: string;\n /** The AI provider that served the request (e.g. `openai`) */\n provider?: string;\n /**\n * The kind of operation performed, e.g. `chat`, `text_completion`,\n * `embeddings`. Last-write-wins across calls.\n */\n operationName?: string;\n /** The provider's identifier for the response, e.g. `chatcmpl-...`. */\n responseId?: string;\n /**\n * Why generation stopped, as reported by the provider, e.g. `[\"stop\"]`,\n * `[\"length\"]`, or `[\"tool_calls\"]`. Last-write-wins across calls.\n */\n finishReasons?: string[];\n\n // Token usage (summed).\n\n /** The number of input tokens consumed by the request. */\n inputTokens?: number;\n /** The number of output tokens produced by the response. */\n outputTokens?: number;\n /** The total tokens consumed, as reported by the provider. */\n totalTokens?: number;\n /**\n * Cached input tokens read from the prompt cache. Providers differ on\n * accounting. OpenAI's cached tokens are a subset of\n * {@link AIMetadata.inputTokens}, Anthropic's are additive. Callers must\n * not assume a single relationship.\n */\n cacheReadTokens?: number;\n /** Tokens written to the prompt cache. */\n cacheCreationTokens?: number;\n /** Reasoning/thinking tokens, when the emitter reports them separately. */\n reasoningTokens?: number;\n\n // Request / inference parameters (last-write-wins). These describe the\n // request the caller made, not the response, and are stored raw as the\n // emitter reports them.\n\n /** Sampling temperature requested, e.g. `0.7`. */\n temperature?: number;\n /** Nucleus sampling probability mass requested (`top_p`), e.g. `0.9`. */\n topP?: number;\n /** Upper bound on tokens to generate (`max_tokens`). */\n maxTokens?: number;\n /** Frequency penalty requested. */\n frequencyPenalty?: number;\n /** Presence penalty requested. */\n presencePenalty?: number;\n /** Sampling seed requested, when the emitter reports it. */\n seed?: number;\n}\n\ntype ValueType = \"text\" | \"number\" | \"stringList\";\ntype MergeStrategy = \"replace\" | \"sum\";\n\n/**\n * Derives the subset of AIMetadata keys whose assigned value matches TValue.\n *\n * AIMetadata fields are optional (`model?: string`), so each field's type is\n * effectively `string | undefined` or `number | undefined`. `Exclude` removes\n * `undefined` before checking the value type, and `-?` prevents optional fields\n * from adding `undefined` back into the final key union.\n */\ntype FieldsWithValue<TValue> = {\n [TField in keyof AIMetadata]-?: Exclude<\n AIMetadata[TField],\n undefined\n > extends TValue\n ? TField\n : never;\n}[keyof AIMetadata];\n\ntype TextField = FieldsWithValue<string>;\ntype NumberField = FieldsWithValue<number>;\ntype StringListField = FieldsWithValue<string[]>;\n\ninterface BaseFieldSpec {\n /** The canonical (preferred) source `gen_ai.*` attribute key. */\n source: string;\n /**\n * Deprecated source keys to read when {@link BaseFieldSpec.source} is absent\n * or carries no usable value, in descending precedence. The preferred\n * {@link BaseFieldSpec.source} always wins when it supplies a value; these are\n * only consulted as fallbacks.\n */\n fallbackSources?: readonly string[];\n valueType: ValueType;\n merge: MergeStrategy;\n}\n\ninterface TextFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: TextField;\n valueType: \"text\";\n merge: \"replace\";\n}\n\ninterface NumberFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: NumberField;\n valueType: \"number\";\n}\n\ninterface StringListFieldSpec extends BaseFieldSpec {\n /** The canonical {@link AIMetadata} field this populates. */\n field: StringListField;\n valueType: \"stringList\";\n merge: \"replace\";\n}\n\ntype FieldSpec = TextFieldSpec | NumberFieldSpec | StringListFieldSpec;\n\nfunction textField(\n field: TextField,\n source: string,\n fallbackSources?: readonly string[],\n): TextFieldSpec {\n return {\n field,\n source,\n fallbackSources,\n valueType: \"text\",\n merge: \"replace\",\n };\n}\n\nfunction numberField(\n field: NumberField,\n source: string,\n merge: MergeStrategy,\n): NumberFieldSpec {\n return {\n field,\n source,\n valueType: \"number\",\n merge,\n };\n}\n\nfunction stringListField(\n field: StringListField,\n source: string,\n): StringListFieldSpec {\n return {\n field,\n source,\n valueType: \"stringList\",\n merge: \"replace\",\n };\n}\n\n/**\n * Every `gen_ai.*` attribute we capture, mapped to its canonical field.\n *\n * Anything not listed here (prompt/response content, messages, tool calls,\n * unknown keys) is ignored.\n */\nexport const FIELD_SPECS = [\n // Identity & classification.\n textField(\"requestModel\", \"gen_ai.request.model\"),\n textField(\"responseModel\", \"gen_ai.response.model\"),\n // `gen_ai.system` is the deprecated predecessor of `gen_ai.provider.name`;\n // the canonical key wins when both are present on a span.\n textField(\"provider\", \"gen_ai.provider.name\", [\"gen_ai.system\"]),\n textField(\"operationName\", \"gen_ai.operation.name\"),\n textField(\"responseId\", \"gen_ai.response.id\"),\n stringListField(\"finishReasons\", \"gen_ai.response.finish_reasons\"),\n\n // Token usage.\n numberField(\"inputTokens\", \"gen_ai.usage.input_tokens\", \"sum\"),\n numberField(\"outputTokens\", \"gen_ai.usage.output_tokens\", \"sum\"),\n numberField(\"totalTokens\", \"gen_ai.usage.total_tokens\", \"sum\"),\n numberField(\"cacheReadTokens\", \"gen_ai.usage.cache_read.input_tokens\", \"sum\"),\n numberField(\n \"cacheCreationTokens\",\n \"gen_ai.usage.cache_creation.input_tokens\",\n \"sum\",\n ),\n numberField(\"reasoningTokens\", \"gen_ai.usage.reasoning.output_tokens\", \"sum\"),\n\n // Request / inference parameters. These describe the request, not a quantity,\n // so they replace rather than sum when a step makes several calls.\n numberField(\"temperature\", \"gen_ai.request.temperature\", \"replace\"),\n numberField(\"topP\", \"gen_ai.request.top_p\", \"replace\"),\n numberField(\"maxTokens\", \"gen_ai.request.max_tokens\", \"replace\"),\n numberField(\n \"frequencyPenalty\",\n \"gen_ai.request.frequency_penalty\",\n \"replace\",\n ),\n numberField(\"presencePenalty\", \"gen_ai.request.presence_penalty\", \"replace\"),\n numberField(\"seed\", \"gen_ai.request.seed\", \"replace\"),\n] as const satisfies readonly FieldSpec[];\n\nfunction parseNumericAttribute(value: unknown): number | undefined {\n if (typeof value === \"number\") {\n return Number.isFinite(value) ? value : undefined;\n }\n\n if (typeof value !== \"string\" || value === \"\") {\n return undefined;\n }\n\n const num = Number(value);\n return Number.isFinite(num) ? num : undefined;\n}\n\n/**\n * Coerces an attribute to a list of non-empty strings, or `undefined` when it\n * yields none. OTLP arrays may carry non-string or empty entries; those are\n * dropped, and an array that reduces to nothing is treated as absent.\n */\nfunction parseStringListAttribute(value: unknown): string[] | undefined {\n const items = Array.isArray(value) ? value : [value];\n const strings = items.filter(\n (item): item is string => typeof item === \"string\" && item !== \"\",\n );\n return strings.length > 0 ? strings : undefined;\n}\n\n/**\n * Extracts {@link AIMetadata} from a span's attributes.\n *\n * Only the allowlisted {@link FIELD_SPECS} are read. Every other attribute is\n * ignored. Numeric fields accept numbers and quoted numeric strings and are\n * otherwise dropped, as OTLP/JSON may encode int64 as a quoted string. Text\n * fields are dropped when empty. String-list fields keep only non-empty string\n * entries and are dropped when none remain.\n *\n * Returns an empty object for spans carrying no recognised `gen_ai.*`\n * attributes.\n *\n * @param attributes - The span attributes, as exposed by\n * `ReadableSpan.attributes`.\n */\nexport const extractAIMetadataFromAttributes = (\n attributes: Attributes,\n): AIMetadata => {\n const metadata: AIMetadata = {};\n\n for (const spec of FIELD_SPECS) {\n // Read the canonical key first, then any deprecated fallbacks in order,\n // taking the first that yields a usable value.\n const sources = [spec.source, ...(spec.fallbackSources ?? [])];\n\n for (const source of sources) {\n const value = attributes[source];\n if (value === undefined) {\n continue;\n }\n\n if (spec.valueType === \"text\") {\n const text = typeof value === \"string\" ? value : String(value);\n if (text !== \"\") {\n metadata[spec.field] = text;\n break;\n }\n } else if (spec.valueType === \"stringList\") {\n const list = parseStringListAttribute(value);\n if (list !== undefined) {\n metadata[spec.field] = list;\n break;\n }\n } else {\n const num = parseNumericAttribute(value);\n if (num !== undefined) {\n metadata[spec.field] = num;\n break;\n }\n }\n }\n }\n\n return metadata;\n};\n\n/**\n * Aggregates two {@link AIMetadata} values, folding a later call's metadata into\n * an earlier accumulator for a single step.\n *\n * - Token-count fields (`merge: \"sum\"`) are **summed**, so a step that makes\n * several LLM calls reports their combined usage.\n * - Every other field uses `merge: \"replace\"` (`b` overwrites `a`)\n *\n * A field is present in the result only when at least one input supplies it.\n *\n * @param a - The accumulator (earlier calls).\n * @param b - The later metadata, whose values win on conflict (and add for sums).\n */\nexport const aggregate = (a: AIMetadata, b: AIMetadata): AIMetadata => {\n const out: AIMetadata = { ...a };\n\n for (const spec of FIELD_SPECS) {\n if (spec.valueType === \"number\") {\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n\n if (spec.merge === \"sum\") {\n out[spec.field] = (out[spec.field] ?? 0) + bValue;\n } else {\n out[spec.field] = bValue;\n }\n } else if (spec.valueType === \"text\") {\n // Text fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n } else {\n // String-list fields are always replace.\n const bValue = b[spec.field];\n if (bValue === undefined) {\n continue;\n }\n out[spec.field] = bValue;\n }\n }\n\n return out;\n};\n\n/** Converts a canonical camelCase field name to the server's snake_case key. */\nconst toSnakeCase = (field: string): string =>\n field.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);\n\n/**\n * Maps an {@link AIMetadata} value onto the server's `inngest.ai` metadata\n * values, converting each canonical camelCase field to snake_case (e.g.\n * `inputTokens` → `input_tokens`). Returns `undefined` when there is nothing to\n * emit so callers can skip stamping metadata entirely.\n */\nexport const toInngestAIMetadataValues = (\n metadata: AIMetadata,\n): Record<string, unknown> | undefined => {\n const entries = Object.entries(metadata);\n if (entries.length === 0) {\n return undefined;\n }\n\n const values: Record<string, unknown> = {};\n for (const [field, value] of entries) {\n values[toSnakeCase(field)] = value;\n }\n\n return values;\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inngest",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"description": "Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.",
|
|
5
5
|
"main": "./index.cjs",
|
|
6
6
|
"module": "./index.js",
|
package/version.cjs
CHANGED
package/version.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.cjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Generated by genversion.\nexport const version = \"4.
|
|
1
|
+
{"version":3,"file":"version.cjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Generated by genversion.\nexport const version = \"4.11.0\";\n"],"mappings":";;AACA,MAAa,UAAU"}
|
package/version.d.cts
CHANGED
package/version.d.ts
CHANGED
package/version.js
CHANGED
package/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Generated by genversion.\nexport const version = \"4.
|
|
1
|
+
{"version":3,"file":"version.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["// Generated by genversion.\nexport const version = \"4.11.0\";\n"],"mappings":";AACA,MAAa,UAAU"}
|