@oxygen-agent/cli 1.987.20 → 1.1010.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 (176) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.d.ts +0 -2
  3. package/dist/admin-primary-providers-render.js +1 -1
  4. package/dist/browser-login.js +1 -4
  5. package/dist/column-decision-options.d.ts +20 -0
  6. package/dist/column-decision-options.js +54 -0
  7. package/dist/command-manifest.d.ts +3 -2
  8. package/dist/command-manifest.js +25 -2
  9. package/dist/credentials.d.ts +1 -1
  10. package/dist/functions-commands.js +13 -9
  11. package/dist/help.d.ts +8 -0
  12. package/dist/help.js +46 -0
  13. package/dist/index.js +2751 -242
  14. package/dist/knowledge-mirror.d.ts +2 -2
  15. package/dist/runtime.d.ts +0 -15
  16. package/dist/runtime.js +1 -1
  17. package/dist/search-ai-filter-notice.d.ts +17 -0
  18. package/dist/search-ai-filter-notice.js +38 -0
  19. package/dist/session.d.ts +4 -3
  20. package/dist/skills.d.ts +8 -7
  21. package/dist/skills.js +58 -20
  22. package/dist/transcript.d.ts +2 -1
  23. package/dist/util.d.ts +10 -1
  24. package/dist/util.js +14 -2
  25. package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
  26. package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
  27. package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
  28. package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
  29. package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
  30. package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
  31. package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
  32. package/node_modules/@oxygen/formula/dist/hash.js +199 -0
  33. package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
  34. package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
  35. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
  36. package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
  37. package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
  38. package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
  39. package/node_modules/@oxygen/shared/dist/billing.js +195 -40
  40. package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
  41. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
  42. package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
  43. package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
  44. package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
  45. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  46. package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
  47. package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
  48. package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
  49. package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
  50. package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
  51. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
  52. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
  53. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
  54. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
  55. package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
  56. package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
  57. package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
  58. package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
  59. package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
  60. package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
  61. package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
  62. package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
  63. package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
  64. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
  65. package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
  66. package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
  67. package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
  68. package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
  69. package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
  70. package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
  71. package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
  72. package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
  73. package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
  74. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
  75. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
  76. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
  77. package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
  78. package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
  79. package/node_modules/@oxygen/shared/dist/index.js +15 -0
  80. package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
  81. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
  82. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
  83. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
  84. package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
  85. package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
  86. package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
  87. package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
  88. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
  89. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
  90. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
  91. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
  92. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
  93. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
  94. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
  95. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
  96. package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
  97. package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
  98. package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
  99. package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
  100. package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
  101. package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
  102. package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
  103. package/node_modules/@oxygen/shared/dist/log.js +6 -1
  104. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
  105. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
  106. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
  107. package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
  108. package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
  109. package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
  110. package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
  111. package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
  112. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
  113. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
  114. package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
  115. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
  116. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
  117. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
  118. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
  119. package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
  120. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
  121. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
  122. package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
  123. package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
  124. package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
  125. package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
  126. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
  127. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
  128. package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
  129. package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
  130. package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
  131. package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
  132. package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
  133. package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
  134. package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
  135. package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
  136. package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
  137. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
  138. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
  139. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
  140. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
  141. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
  142. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
  143. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
  144. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
  145. package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
  146. package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
  147. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
  148. package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
  149. package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
  150. package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
  151. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
  152. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
  153. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
  154. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
  155. package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
  156. package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
  157. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
  158. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
  159. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
  160. package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
  161. package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
  162. package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
  163. package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
  164. package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
  165. package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
  166. package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
  167. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
  168. package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
  169. package/node_modules/@oxygen/shared/dist/version.js +8 -1
  170. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
  171. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
  172. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
  173. package/node_modules/@oxygen/shared/package.json +59 -0
  174. package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
  175. package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
  176. package/package.json +3 -2
@@ -0,0 +1,79 @@
1
+ import { type LogLevel, type LogSink } from "./log.js";
2
+ /**
3
+ * Ships `log()` records to an OTLP/HTTP logs receiver (the self-hosted SigNoz
4
+ * collector) instead of — or alongside — Axiom.
5
+ *
6
+ * WHY HAND-ROLLED. This is a few dozen lines of JSON against a stable, versioned
7
+ * wire format. `@opentelemetry/exporter-logs-otlp-http` would pull the OTel logs
8
+ * SDK, a LoggerProvider, a BatchLogRecordProcessor and their transitive protobuf
9
+ * machinery into a package that every workspace imports — including edge-safe
10
+ * ones — to serialize a shape this module states in full below. The exporter
11
+ * also owns its own batching, so the drop-oldest/never-block/never-throw
12
+ * contract the two existing shippers hold would become the SDK's opinion rather
13
+ * than ours. OTLP/JSON is chosen over protobuf for the same reason: no codegen,
14
+ * no dependency, and a body an operator can read with `curl` when a collector
15
+ * silently rejects a batch.
16
+ *
17
+ * DELIVERY IS BEST-EFFORT BY CONSTRUCTION, exactly like
18
+ * `apps/worker/src/log-shipper.ts`: the sink only enqueues (it is called from
19
+ * inside `log()`, so it must never await, throw, or block), the queue drops the
20
+ * OLDEST record on overflow because the newest lines describe the incident being
21
+ * debugged, and a collector outage is a dropped batch rather than a process
22
+ * failure. Failures and queue overflow are reported through `log()`, at most
23
+ * once per minute each whatever the flush interval (a dead collector behind a
24
+ * 2 s flush would otherwise write ~43k lines a day into Axiom), with the
25
+ * failures and drops a suppressed report would have carried accumulated into
26
+ * the next one, under the re-entrancy guard the Axiom shippers use
27
+ * (`emittingInternal`): this sink skips the records it emits itself, so a broken
28
+ * collector cannot feed its own failure line back into its queue, while every
29
+ * sink composed beside it (Axiom under `OXYGEN_LOG_SINK=both`) and stdout still
30
+ * receive the line. `log()` always writes stdout before it calls a sink, which
31
+ * is why the former direct stderr write is not kept as a fallback.
32
+ */
33
+ export type OtlpLogSinkOptions = {
34
+ /** Full receiver URL, e.g. `http://collector.internal:4318/v1/logs`. */
35
+ endpoint: string;
36
+ /** Extra request headers (collector auth); see `parseOtlpHeaders`. */
37
+ headers?: Record<string, string> | undefined;
38
+ /** `service.name` resource attribute. */
39
+ serviceName: string;
40
+ /** Resource attributes merged after the defaults, so a caller can override them. */
41
+ resource?: Record<string, string | null | undefined> | undefined;
42
+ batchSize?: number | undefined;
43
+ /** `0` disables the interval timer, for runtimes that flush off a request lifecycle. */
44
+ flushIntervalMs?: number | undefined;
45
+ maxQueue?: number | undefined;
46
+ /** Clock for the report throttle; tests only. */
47
+ now?: (() => number) | undefined;
48
+ /**
49
+ * Lowest level this sink ships; records below it never enter the queue. Null
50
+ * or absent ships every level. Sinks composed beside this one (Axiom) and
51
+ * stdout are unaffected; see `resolveOtlpLogMinLevel`.
52
+ */
53
+ minLevel?: LogLevel | null | undefined;
54
+ fetch?: typeof globalThis.fetch | undefined;
55
+ };
56
+ export type OtlpLogSinkHandle = {
57
+ sink: LogSink;
58
+ flush: () => Promise<void>;
59
+ stop: () => Promise<void>;
60
+ };
61
+ /**
62
+ * Reads `OXYGEN_LOG_OTLP_MIN_LEVEL` (debug|info|warn|error, case-insensitive).
63
+ *
64
+ * The OTLP collector is the one transport a noisy tier can drown, so it alone
65
+ * gets a floor; Axiom and stdout keep every level. Unset ships everything, which
66
+ * is the behaviour before the variable existed. An unrecognised value is named
67
+ * through `log()` and treated as unset: dropping more than was asked is the
68
+ * failure an operator cannot see, so a typo errs toward shipping.
69
+ */
70
+ export declare function resolveOtlpLogMinLevel(env?: NodeJS.ProcessEnv): LogLevel | null;
71
+ /**
72
+ * `k=v,k=v` — the same shape as `OTEL_EXPORTER_OTLP_HEADERS`, so an operator who
73
+ * knows the OTel variable can set ours without learning a second syntax. A pair
74
+ * with no `=`, or an empty key, is skipped rather than throwing: this is parsed
75
+ * at process start, and a malformed telemetry header must not stop a boot. The
76
+ * value keeps every `=` after the first (tokens contain them).
77
+ */
78
+ export declare function parseOtlpHeaders(raw: string | null | undefined): Record<string, string>;
79
+ export declare function createOtlpLogSink(options: OtlpLogSinkOptions): OtlpLogSinkHandle;
@@ -0,0 +1,366 @@
1
+ import { log } from "./log.js";
2
+ import { resolveDeployEnv } from "./deploy-env.js";
3
+ import { sanitizeLogFields } from "./redaction.js";
4
+ import { trace } from "@opentelemetry/api";
5
+ import { operationalLogSnapshot } from "./operational-telemetry.js";
6
+ import { telemetryEndpointForLog } from "./telemetry-export-observer.js";
7
+ const DEFAULT_BATCH_SIZE = 500;
8
+ const DEFAULT_FLUSH_INTERVAL_MS = 2_000;
9
+ const DEFAULT_MAX_QUEUE = 10_000;
10
+ const EXPORT_TIMEOUT_MS = 10_000;
11
+ const REPORT_WINDOW_MS = 60_000;
12
+ const MAX_RESPONSE_BYTES = 65_536;
13
+ async function rejectedRecords(response, batchLength) {
14
+ const reader = response.body?.getReader();
15
+ if (!reader)
16
+ return 0;
17
+ const decoder = new TextDecoder();
18
+ let text = "";
19
+ let bytes = 0;
20
+ try {
21
+ for (;;) {
22
+ const chunk = await reader.read();
23
+ if (chunk.done)
24
+ break;
25
+ bytes += chunk.value.byteLength;
26
+ if (bytes > MAX_RESPONSE_BYTES)
27
+ throw new Error("invalid_response");
28
+ text += decoder.decode(chunk.value, { stream: true });
29
+ }
30
+ text += decoder.decode();
31
+ if (!text.trim())
32
+ return 0;
33
+ const data = JSON.parse(text);
34
+ if (!data || typeof data !== "object" || Array.isArray(data))
35
+ throw new Error("invalid_response");
36
+ const partial = data.partialSuccess;
37
+ const rejected = Number(partial?.rejectedLogRecords ?? 0);
38
+ if (!Number.isSafeInteger(rejected) || rejected < 0 || rejected > batchLength)
39
+ throw new Error("invalid_response");
40
+ return rejected;
41
+ }
42
+ finally {
43
+ await reader.cancel().catch(() => undefined);
44
+ reader.releaseLock();
45
+ }
46
+ }
47
+ // OTLP severity numbers (logs data model): the base of each 4-value band.
48
+ const SEVERITY_NUMBERS = { debug: 5, info: 9, warn: 13, error: 17 };
49
+ // Set by log() on every record; they become the OTLP record's own fields rather
50
+ // than attributes, so carrying them twice would just inflate every batch.
51
+ const RECORD_FIELDS_NOT_ATTRIBUTES = new Set(["ts", "level", "msg", "trace_id", "span_id"]);
52
+ /**
53
+ * Reads `OXYGEN_LOG_OTLP_MIN_LEVEL` (debug|info|warn|error, case-insensitive).
54
+ *
55
+ * The OTLP collector is the one transport a noisy tier can drown, so it alone
56
+ * gets a floor; Axiom and stdout keep every level. Unset ships everything, which
57
+ * is the behaviour before the variable existed. An unrecognised value is named
58
+ * through `log()` and treated as unset: dropping more than was asked is the
59
+ * failure an operator cannot see, so a typo errs toward shipping.
60
+ */
61
+ export function resolveOtlpLogMinLevel(env = process.env) {
62
+ const raw = env.OXYGEN_LOG_OTLP_MIN_LEVEL?.trim().toLowerCase();
63
+ if (!raw)
64
+ return null;
65
+ if (Object.hasOwn(SEVERITY_NUMBERS, raw))
66
+ return raw;
67
+ log("warn", "otlp_log_sink.min_level_invalid", {
68
+ min_level: raw.slice(0, 32),
69
+ allowed: Object.keys(SEVERITY_NUMBERS).join("|"),
70
+ });
71
+ return null;
72
+ }
73
+ /**
74
+ * The `code` of a transport error's `cause` (undici puts ECONNREFUSED,
75
+ * UND_ERR_CONNECT_TIMEOUT and the like there), and nothing else: the message
76
+ * and the error itself can carry the request URL and, with it, collector
77
+ * tokens. Anything that is not a short identifier-shaped code is dropped.
78
+ */
79
+ function sanitizedErrorCause(error) {
80
+ const cause = error && typeof error === "object" ? error.cause : undefined;
81
+ const code = cause && typeof cause === "object" ? cause.code : undefined;
82
+ return typeof code === "string" && /^[A-Z][A-Z0-9_]{1,63}$/.test(code) ? code : null;
83
+ }
84
+ /**
85
+ * `k=v,k=v` — the same shape as `OTEL_EXPORTER_OTLP_HEADERS`, so an operator who
86
+ * knows the OTel variable can set ours without learning a second syntax. A pair
87
+ * with no `=`, or an empty key, is skipped rather than throwing: this is parsed
88
+ * at process start, and a malformed telemetry header must not stop a boot. The
89
+ * value keeps every `=` after the first (tokens contain them).
90
+ */
91
+ export function parseOtlpHeaders(raw) {
92
+ const headers = {};
93
+ if (!raw)
94
+ return headers;
95
+ for (const pair of raw.split(",")) {
96
+ const separator = pair.indexOf("=");
97
+ if (separator <= 0)
98
+ continue;
99
+ const key = pair.slice(0, separator).trim();
100
+ const value = pair.slice(separator + 1).trim();
101
+ if (!key)
102
+ continue;
103
+ headers[key] = value;
104
+ }
105
+ return headers;
106
+ }
107
+ function toAnyValue(value) {
108
+ if (typeof value === "string")
109
+ return { stringValue: value };
110
+ if (typeof value === "boolean")
111
+ return { boolValue: value };
112
+ if (typeof value === "number") {
113
+ if (!Number.isFinite(value))
114
+ return { stringValue: String(value) };
115
+ // OTLP/JSON encodes int64 as a STRING (proto3 JSON mapping); a bare number
116
+ // there is what makes a collector reject an otherwise valid record.
117
+ return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
118
+ }
119
+ if (value === null || value === undefined)
120
+ return null;
121
+ try {
122
+ return { stringValue: JSON.stringify(value) ?? String(value) };
123
+ }
124
+ catch {
125
+ return { stringValue: "[unserializable]" };
126
+ }
127
+ }
128
+ function toAttributes(source, skip) {
129
+ const attributes = [];
130
+ for (const [key, value] of Object.entries(source)) {
131
+ if (skip?.has(key))
132
+ continue;
133
+ const anyValue = toAnyValue(value);
134
+ if (!anyValue)
135
+ continue;
136
+ attributes.push({ key, value: anyValue });
137
+ }
138
+ return attributes;
139
+ }
140
+ function timeUnixNano(ts) {
141
+ const parsed = typeof ts === "string" ? Date.parse(ts) : Number.NaN;
142
+ const millis = Number.isFinite(parsed) ? parsed : Date.now();
143
+ // Decimal string, not a number: nanoseconds since the epoch exceed 2^53.
144
+ return `${millis}000000`;
145
+ }
146
+ /**
147
+ * The identity contract's tier when `OXYGEN_DEPLOY_ENV` is set, else null. An
148
+ * invalid value is the boot guard's to refuse; a telemetry sink created before
149
+ * that guard must not throw on it and take the logger down with it.
150
+ */
151
+ function deployTierOrNull() {
152
+ if (!process.env.OXYGEN_DEPLOY_ENV)
153
+ return null;
154
+ try {
155
+ return resolveDeployEnv().tier;
156
+ }
157
+ catch {
158
+ return null;
159
+ }
160
+ }
161
+ function recordLevel(record) {
162
+ return typeof record.level === "string" && Object.hasOwn(SEVERITY_NUMBERS, record.level)
163
+ ? record.level
164
+ : "info";
165
+ }
166
+ function toLogRecord(record) {
167
+ const level = recordLevel(record);
168
+ return {
169
+ timeUnixNano: timeUnixNano(record.ts),
170
+ severityNumber: SEVERITY_NUMBERS[level],
171
+ severityText: level.toUpperCase(),
172
+ body: { stringValue: typeof record.msg === "string" ? record.msg : String(record.msg ?? "") },
173
+ ...(typeof record.trace_id === "string" && /^[a-f\d]{32}$/i.test(record.trace_id)
174
+ && !/^0+$/.test(record.trace_id) ? { traceId: record.trace_id.toLowerCase() } : {}),
175
+ ...(typeof record.span_id === "string" && /^[a-f\d]{16}$/i.test(record.span_id)
176
+ && !/^0+$/.test(record.span_id) ? { spanId: record.span_id.toLowerCase() } : {}),
177
+ attributes: toAttributes(record, RECORD_FIELDS_NOT_ATTRIBUTES),
178
+ };
179
+ }
180
+ export function createOtlpLogSink(options) {
181
+ const batchSize = options.batchSize && options.batchSize > 0 ? options.batchSize : DEFAULT_BATCH_SIZE;
182
+ const maxQueue = options.maxQueue && options.maxQueue > 0 ? options.maxQueue : DEFAULT_MAX_QUEUE;
183
+ const flushIntervalMs = options.flushIntervalMs === undefined
184
+ ? DEFAULT_FLUSH_INTERVAL_MS
185
+ : options.flushIntervalMs;
186
+ const doFetch = options.fetch ?? globalThis.fetch;
187
+ const now = options.now ?? Date.now;
188
+ const minSeverity = options.minLevel ? SEVERITY_NUMBERS[options.minLevel] : null;
189
+ // A null/undefined override is dropped rather than applied, so a caller that
190
+ // resolves its own environment and comes back empty-handed falls back to the
191
+ // default here instead of shipping records with no `deployment.environment`.
192
+ const resourceOverrides = Object.fromEntries(Object.entries(options.resource ?? {}).filter(([, value]) => value !== null && value !== undefined));
193
+ const resourceAttributes = toAttributes({
194
+ "service.name": options.serviceName,
195
+ "deployment.environment": deployTierOrNull()
196
+ ?? process.env.VERCEL_ENV
197
+ ?? process.env.NODE_ENV
198
+ ?? null,
199
+ "deployment.sha": process.env.OXYGEN_GIT_SHA ?? process.env.VERCEL_GIT_COMMIT_SHA,
200
+ ...resourceOverrides,
201
+ });
202
+ const queue = [];
203
+ let inFlight = null;
204
+ let stopped = false;
205
+ let lastReportAt = Number.NEGATIVE_INFINITY;
206
+ let lastOverflowReportAt = Number.NEGATIVE_INFINITY;
207
+ // Failed exports a throttled report skipped, and the records they dropped;
208
+ // both ride on the next export_failed line, as telemetry.export_failed does.
209
+ let suppressedFailures = 0;
210
+ let pendingDropped = 0;
211
+ // Records the overflow path dropped that no report has carried yet.
212
+ let droppedOverflow = 0;
213
+ // Re-entrancy guard, as in apps/worker/src/log-shipper.ts. log() calls this
214
+ // sink synchronously, so while it is set the record on offer is this sink's
215
+ // own report: skipped here, so a failing flush never enqueues its own error
216
+ // line and re-fails on it forever.
217
+ let emittingInternal = false;
218
+ const endpointForLog = telemetryEndpointForLog(options.endpoint);
219
+ const emitInternal = (msg, fields) => {
220
+ emittingInternal = true;
221
+ try {
222
+ log("warn", msg, { endpoint: endpointForLog, ...fields });
223
+ }
224
+ catch {
225
+ // A telemetry failure that cannot even be reported is still not a fault
226
+ // of the code being observed.
227
+ }
228
+ finally {
229
+ emittingInternal = false;
230
+ }
231
+ };
232
+ const report = (droppedEvents, fields) => {
233
+ pendingDropped += droppedEvents;
234
+ const at = now();
235
+ if (at - lastReportAt < REPORT_WINDOW_MS) {
236
+ suppressedFailures += 1;
237
+ return;
238
+ }
239
+ lastReportAt = at;
240
+ const overflow = droppedOverflow;
241
+ const dropped = pendingDropped;
242
+ const suppressed = suppressedFailures;
243
+ droppedOverflow = 0;
244
+ pendingDropped = 0;
245
+ suppressedFailures = 0;
246
+ emitInternal("otlp_log_sink.export_failed", {
247
+ ...fields,
248
+ dropped_events: dropped,
249
+ dropped_overflow: overflow,
250
+ suppressed_failures: suppressed,
251
+ });
252
+ };
253
+ // Overflow is data loss even while every export succeeds (a burst faster than
254
+ // the collector drains), so it is reported on its own when no failure line
255
+ // has carried it.
256
+ const reportOverflow = () => {
257
+ if (droppedOverflow === 0)
258
+ return;
259
+ const at = now();
260
+ if (at - lastOverflowReportAt < REPORT_WINDOW_MS)
261
+ return;
262
+ lastOverflowReportAt = at;
263
+ const overflow = droppedOverflow;
264
+ droppedOverflow = 0;
265
+ emitInternal("otlp_log_sink.queue_overflow", { dropped_events: overflow });
266
+ };
267
+ const sink = (record) => {
268
+ if (stopped || emittingInternal)
269
+ return;
270
+ // An unknown level ships as INFO (see toLogRecord), so it filters as one.
271
+ if (minSeverity !== null && SEVERITY_NUMBERS[recordLevel(record)] < minSeverity)
272
+ return;
273
+ // Drop the OLDEST record and keep this one: under sustained backpressure the
274
+ // newest lines are the ones describing the incident being debugged.
275
+ while (queue.length >= maxQueue) {
276
+ queue.shift();
277
+ droppedOverflow += 1;
278
+ }
279
+ const activeSpan = trace.getActiveSpan()?.spanContext();
280
+ queue.push(operationalLogSnapshot(sanitizeLogFields({
281
+ ...record,
282
+ ...(activeSpan && (!record.trace_id || record.trace_id === activeSpan.traceId)
283
+ ? { trace_id: activeSpan.traceId, span_id: activeSpan.spanId }
284
+ : {}),
285
+ })));
286
+ };
287
+ const flush = async () => {
288
+ if (inFlight)
289
+ return inFlight;
290
+ if (queue.length === 0)
291
+ return undefined;
292
+ const batch = queue.splice(0, batchSize);
293
+ const payload = JSON.stringify({
294
+ resourceLogs: [
295
+ {
296
+ resource: { attributes: resourceAttributes },
297
+ scopeLogs: [
298
+ {
299
+ scope: { name: "oxygen" },
300
+ logRecords: batch.map(toLogRecord),
301
+ },
302
+ ],
303
+ },
304
+ ],
305
+ });
306
+ const run = (async () => {
307
+ try {
308
+ const response = await doFetch(options.endpoint, {
309
+ method: "POST",
310
+ headers: { "Content-Type": "application/json", ...options.headers },
311
+ body: payload,
312
+ signal: AbortSignal.timeout(EXPORT_TIMEOUT_MS),
313
+ });
314
+ if (response.ok) {
315
+ try {
316
+ const rejected = await rejectedRecords(response, batch.length);
317
+ if (rejected)
318
+ report(rejected, { status: response.status, error_code: "partial_rejection" });
319
+ }
320
+ catch {
321
+ report(batch.length, { status: response.status, error_code: "invalid_response" });
322
+ }
323
+ return;
324
+ }
325
+ // Receivers/proxies can echo rejected payloads or credentials. Never
326
+ // read arbitrary response text into operational diagnostics.
327
+ await response.body?.cancel().catch(() => undefined);
328
+ // The batch is gone deliberately: retrying into an unhealthy collector
329
+ // is how a telemetry path turns an ingest blip into memory pressure.
330
+ report(batch.length, { status: response.status, error_code: "receiver_rejected" });
331
+ }
332
+ catch (error) {
333
+ report(batch.length, {
334
+ error_code: "transport_failed",
335
+ error_cause: sanitizedErrorCause(error),
336
+ });
337
+ }
338
+ finally {
339
+ reportOverflow();
340
+ }
341
+ })();
342
+ inFlight = run.finally(() => {
343
+ inFlight = null;
344
+ });
345
+ return inFlight;
346
+ };
347
+ const timer = flushIntervalMs > 0
348
+ ? setInterval(() => {
349
+ void flush();
350
+ }, flushIntervalMs)
351
+ : null;
352
+ // Never hold the process open on the telemetry timer.
353
+ timer?.unref?.();
354
+ const stop = async () => {
355
+ stopped = true;
356
+ if (timer)
357
+ clearInterval(timer);
358
+ // Drain rather than flush once: a shutdown's final lines are usually the
359
+ // interesting ones, and one flush ships only the oldest `batchSize`.
360
+ while (queue.length > 0) {
361
+ await flush();
362
+ }
363
+ await inFlight;
364
+ };
365
+ return { sink, flush, stop };
366
+ }
@@ -50,6 +50,7 @@ export const PLAN_GATED_CAPABILITIES = [
50
50
  * `required_plan_tiers` in tool-access, which the resolver consults separately.
51
51
  */
52
52
  export const PLAN_GATE_QUALIFYING_TIERS = [
53
+ "oxygen",
53
54
  "starter",
54
55
  "pro",
55
56
  "team",
@@ -185,14 +185,14 @@ export declare const PLAN_LIMITS: {
185
185
  readonly maxDirectCsvBodyBytes: 10000000;
186
186
  };
187
187
  readonly agents: {
188
- readonly maxTotalCredits: 2500;
189
- readonly maxInferenceCredits: 1500;
188
+ readonly maxTotalCredits: 250;
189
+ readonly maxInferenceCredits: 150;
190
190
  readonly maxUpstreamCostUsd: 10;
191
191
  readonly maxUpstreamCostUsdByok: 10;
192
192
  };
193
193
  readonly copilot: {
194
- readonly maxSessionBudgetCredits: 5000;
195
- readonly defaultPerTurnCreditCeiling: 1500;
194
+ readonly maxSessionBudgetCredits: 500;
195
+ readonly defaultPerTurnCreditCeiling: 150;
196
196
  };
197
197
  readonly signals: {
198
198
  readonly maxEvents: 100;
@@ -295,14 +295,14 @@ export declare const PLAN_LIMITS: {
295
295
  readonly maxDirectCsvBodyBytes: 10000000;
296
296
  };
297
297
  readonly agents: {
298
- readonly maxTotalCredits: 10000;
299
- readonly maxInferenceCredits: 5000;
298
+ readonly maxTotalCredits: 1000;
299
+ readonly maxInferenceCredits: 500;
300
300
  readonly maxUpstreamCostUsd: 10;
301
301
  readonly maxUpstreamCostUsdByok: 250;
302
302
  };
303
303
  readonly copilot: {
304
- readonly maxSessionBudgetCredits: 5000;
305
- readonly defaultPerTurnCreditCeiling: 1500;
304
+ readonly maxSessionBudgetCredits: 500;
305
+ readonly defaultPerTurnCreditCeiling: 150;
306
306
  };
307
307
  readonly signals: {
308
308
  readonly maxEvents: 200;
@@ -405,14 +405,14 @@ export declare const PLAN_LIMITS: {
405
405
  readonly maxDirectCsvBodyBytes: 10000000;
406
406
  };
407
407
  readonly agents: {
408
- readonly maxTotalCredits: 25000;
409
- readonly maxInferenceCredits: 12500;
408
+ readonly maxTotalCredits: 2500;
409
+ readonly maxInferenceCredits: 1250;
410
410
  readonly maxUpstreamCostUsd: 25;
411
411
  readonly maxUpstreamCostUsdByok: 250;
412
412
  };
413
413
  readonly copilot: {
414
- readonly maxSessionBudgetCredits: 10000;
415
- readonly defaultPerTurnCreditCeiling: 2500;
414
+ readonly maxSessionBudgetCredits: 1000;
415
+ readonly defaultPerTurnCreditCeiling: 250;
416
416
  };
417
417
  readonly signals: {
418
418
  readonly maxEvents: 200;
@@ -515,14 +515,14 @@ export declare const PLAN_LIMITS: {
515
515
  readonly maxDirectCsvBodyBytes: 10000000;
516
516
  };
517
517
  readonly agents: {
518
- readonly maxTotalCredits: 50000;
519
- readonly maxInferenceCredits: 25000;
518
+ readonly maxTotalCredits: 5000;
519
+ readonly maxInferenceCredits: 2500;
520
520
  readonly maxUpstreamCostUsd: 50;
521
521
  readonly maxUpstreamCostUsdByok: 250;
522
522
  };
523
523
  readonly copilot: {
524
- readonly maxSessionBudgetCredits: 20000;
525
- readonly defaultPerTurnCreditCeiling: 5000;
524
+ readonly maxSessionBudgetCredits: 2000;
525
+ readonly defaultPerTurnCreditCeiling: 500;
526
526
  };
527
527
  readonly signals: {
528
528
  readonly maxEvents: 200;
@@ -625,14 +625,14 @@ export declare const PLAN_LIMITS: {
625
625
  readonly maxDirectCsvBodyBytes: 10000000;
626
626
  };
627
627
  readonly agents: {
628
- readonly maxTotalCredits: 100000;
629
- readonly maxInferenceCredits: 50000;
628
+ readonly maxTotalCredits: 10000;
629
+ readonly maxInferenceCredits: 5000;
630
630
  readonly maxUpstreamCostUsd: 100;
631
631
  readonly maxUpstreamCostUsdByok: 250;
632
632
  };
633
633
  readonly copilot: {
634
- readonly maxSessionBudgetCredits: 50000;
635
- readonly defaultPerTurnCreditCeiling: 10000;
634
+ readonly maxSessionBudgetCredits: 5000;
635
+ readonly defaultPerTurnCreditCeiling: 1000;
636
636
  };
637
637
  readonly signals: {
638
638
  readonly maxEvents: 200;
@@ -645,8 +645,9 @@ export declare const PLAN_LIMITS: {
645
645
  };
646
646
  /**
647
647
  * Map a raw plan-tier string (subscription `tier`, metadata tier, legacy plan
648
- * key like `growth_250`) onto its enforcement rung. Unknown/absent tiers land
649
- * on the unentitled "free" rung; enterprise shares "scale".
648
+ * key like `growth_250`, or an Oxygen rung key like `oxygen_499`) onto its
649
+ * enforcement rung. Unknown/absent tiers land on the unentitled "free" rung;
650
+ * enterprise shares "scale".
650
651
  */
651
652
  export declare function limitsTierForPlanTier(tier: string | null | undefined): LimitsTier;
652
653
  export declare function planLimitsForTier(tier: LimitsTier): PlanLimits;