@oxygen-agent/cli 1.982.3 → 1.1003.12
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/README.md +1 -1
- package/dist/admin-primary-providers-render.d.ts +0 -2
- package/dist/admin-primary-providers-render.js +1 -1
- package/dist/browser-login.js +1 -4
- package/dist/command-manifest.d.ts +3 -2
- package/dist/command-manifest.js +10 -0
- package/dist/credentials.d.ts +1 -1
- package/dist/functions-commands.js +27 -7
- package/dist/help.d.ts +29 -0
- package/dist/help.js +139 -0
- package/dist/index.js +1875 -164
- package/dist/knowledge-mirror.d.ts +2 -2
- package/dist/runtime.d.ts +0 -15
- package/dist/runtime.js +1 -1
- package/dist/session.d.ts +4 -3
- package/dist/skills.d.ts +8 -7
- package/dist/skills.js +24 -10
- package/dist/transcript.d.ts +2 -1
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1200
- package/dist/util.d.ts +1 -1
- package/dist/util.js +1 -3
- package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
- package/node_modules/@oxygen/cli-ugc/package.json +15 -0
- package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
- package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +136 -1
- package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
- package/node_modules/@oxygen/formula/dist/hash.js +199 -0
- package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/formula/dist/index.js +1 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +74 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +358 -0
- package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +103 -47
- package/node_modules/@oxygen/shared/dist/billing.js +150 -40
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +114 -16
- package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.js +80 -0
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +14 -10
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +108 -0
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +545 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +103 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +819 -0
- package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
- package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
- package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/shared/dist/index.js +14 -0
- package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +17 -2
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +28 -6
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +57 -8
- package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/linkedin-countries.js +359 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
- package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +6 -1
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +54 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +213 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
- package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
- package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +15 -0
- package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +65 -5
- package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
- package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +49 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +134 -4
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
- package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
- package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +9 -1
- package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
- package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/package.json +60 -0
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
- package/package.json +6 -3
|
@@ -38,6 +38,8 @@ export type LlmSpanBody = {
|
|
|
38
38
|
userId?: string | null;
|
|
39
39
|
};
|
|
40
40
|
export type LlmGenerationBody = LlmSpanBody & {
|
|
41
|
+
/** Persist this physical id when a later annotation must target this attempt. */
|
|
42
|
+
observationId?: string;
|
|
41
43
|
model?: string | null;
|
|
42
44
|
modelParameters?: Record<string, unknown>;
|
|
43
45
|
completionStartTime?: Date | null;
|
|
@@ -63,9 +65,11 @@ export type LlmEventBody = {
|
|
|
63
65
|
* because that variant requires a *string* value. Widening later stays additive.
|
|
64
66
|
*/
|
|
65
67
|
export type LlmScoreBody = {
|
|
66
|
-
/**
|
|
68
|
+
/** Retry-safe scores need the same id, name AND timestamp on every attempt. */
|
|
67
69
|
id?: string;
|
|
68
70
|
traceId: string;
|
|
71
|
+
observationId?: string;
|
|
72
|
+
timestamp?: Date;
|
|
69
73
|
name: string;
|
|
70
74
|
value: number;
|
|
71
75
|
dataType?: "BOOLEAN" | "NUMERIC";
|
|
@@ -103,6 +107,13 @@ export type LlmTracingClient = {
|
|
|
103
107
|
*/
|
|
104
108
|
export declare function isLlmTracingEnabled(env?: EnvMap): boolean;
|
|
105
109
|
export declare function resolveLlmTracingEnvironment(env?: EnvMap): string;
|
|
110
|
+
/**
|
|
111
|
+
* The private Langfuse provider must identify its own runtime rather than
|
|
112
|
+
* inherit the process-wide OTel resource. Fly identity wins because it is
|
|
113
|
+
* injected per machine and cannot drift; a local CLI/test process is neither
|
|
114
|
+
* the web app nor the worker.
|
|
115
|
+
*/
|
|
116
|
+
export declare function resolveLlmTracingServiceName(env?: EnvMap): string;
|
|
106
117
|
/**
|
|
107
118
|
* Deterministic Langfuse trace id for an external seed (copilot turn id, agent
|
|
108
119
|
* run id, AI-column run id).
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
// * Trace-level input/output is deprecated in v5. Overall IO goes on the ROOT
|
|
33
33
|
// observation instead; setTraceIO()/setActiveTraceIO() are deliberately not
|
|
34
34
|
// used here.
|
|
35
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
35
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
36
36
|
import { OXYGEN_VERSION } from "./version.js";
|
|
37
37
|
import { prepareLlmPayload } from "./llm-payload.js";
|
|
38
38
|
import { log } from "./log.js";
|
|
@@ -73,6 +73,15 @@ export function resolveLlmTracingEnvironment(env = process.env) {
|
|
|
73
73
|
const explicit = env.LANGFUSE_TRACING_ENVIRONMENT?.trim().toLowerCase();
|
|
74
74
|
if (explicit)
|
|
75
75
|
return explicit.replace(/[^a-z0-9_-]/g, "-");
|
|
76
|
+
// Self-hosted first: those processes carry neither VERCEL_ENV nor a Fly app
|
|
77
|
+
// name, so without this every self-hosted tier would report "development".
|
|
78
|
+
// selfhost-shadow stays "development" deliberately — production-shaped data,
|
|
79
|
+
// not production traffic.
|
|
80
|
+
const deployEnv = env.OXYGEN_DEPLOY_ENV?.trim().toLowerCase();
|
|
81
|
+
if (deployEnv === "selfhost-prod")
|
|
82
|
+
return "production";
|
|
83
|
+
if (deployEnv === "selfhost-shadow" || deployEnv === "selfhost-dev")
|
|
84
|
+
return "development";
|
|
76
85
|
if (env.VERCEL_ENV)
|
|
77
86
|
return env.VERCEL_ENV === "production" ? "production" : "development";
|
|
78
87
|
const flyApp = env.FLY_APP_NAME ?? env.FLY_APP;
|
|
@@ -83,6 +92,23 @@ export function resolveLlmTracingEnvironment(env = process.env) {
|
|
|
83
92
|
return flyEnv === "production" ? "production" : "development";
|
|
84
93
|
return "development";
|
|
85
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The private Langfuse provider must identify its own runtime rather than
|
|
97
|
+
* inherit the process-wide OTel resource. Fly identity wins because it is
|
|
98
|
+
* injected per machine and cannot drift; a local CLI/test process is neither
|
|
99
|
+
* the web app nor the worker.
|
|
100
|
+
*/
|
|
101
|
+
export function resolveLlmTracingServiceName(env = process.env) {
|
|
102
|
+
const flyApp = env.FLY_APP_NAME?.trim() || env.FLY_APP?.trim();
|
|
103
|
+
if (flyApp)
|
|
104
|
+
return flyApp.includes("-dev") ? "oxygen-worker-dev" : "oxygen-worker";
|
|
105
|
+
const flyEnvironment = env.FLY_ENVIRONMENT?.trim().toLowerCase();
|
|
106
|
+
if (flyEnvironment)
|
|
107
|
+
return flyEnvironment === "production" ? "oxygen-worker" : "oxygen-worker-dev";
|
|
108
|
+
if (env.VERCEL?.trim() || env.VERCEL_ENV?.trim())
|
|
109
|
+
return "oxygen-web";
|
|
110
|
+
return "oxygen-local";
|
|
111
|
+
}
|
|
86
112
|
/**
|
|
87
113
|
* Deterministic Langfuse trace id for an external seed (copilot turn id, agent
|
|
88
114
|
* run id, AI-column run id).
|
|
@@ -140,8 +166,9 @@ function createOtelEmitter(env, warn) {
|
|
|
140
166
|
const init = () => {
|
|
141
167
|
handle ??= (async () => {
|
|
142
168
|
try {
|
|
143
|
-
const [{ LangfuseSpanProcessor }, { BasicTracerProvider, AlwaysOnSampler }, tracing, otel, core] = await Promise.all([
|
|
169
|
+
const [{ LangfuseSpanProcessor }, { BasicTracerProvider, AlwaysOnSampler }, { resourceFromAttributes }, tracing, otel, core] = await Promise.all([
|
|
144
170
|
import("@langfuse/otel"), import("@opentelemetry/sdk-trace-base"),
|
|
171
|
+
import("@opentelemetry/resources"),
|
|
145
172
|
import("@langfuse/tracing"), import("@opentelemetry/api"), import("@langfuse/core"),
|
|
146
173
|
]);
|
|
147
174
|
const processor = new LangfuseSpanProcessor({
|
|
@@ -160,6 +187,11 @@ function createOtelEmitter(env, warn) {
|
|
|
160
187
|
});
|
|
161
188
|
let identity;
|
|
162
189
|
const provider = new BasicTracerProvider({
|
|
190
|
+
// This provider is deliberately isolated from the application's
|
|
191
|
+
// global OTel pipeline, so it also needs its own explicit resource.
|
|
192
|
+
// Never trust OTEL_SERVICE_NAME here: a cross-environment Doppler
|
|
193
|
+
// value must not relabel prompt-bearing Langfuse observations.
|
|
194
|
+
resource: resourceFromAttributes({ "service.name": resolveLlmTracingServiceName(env) }),
|
|
163
195
|
spanProcessors: [{
|
|
164
196
|
onStart: (span) => {
|
|
165
197
|
// Directly pass the private parent claim to the processor. A
|
|
@@ -266,7 +298,7 @@ function createApiScorer(env, warn) {
|
|
|
266
298
|
username: env.LANGFUSE_PUBLIC_KEY,
|
|
267
299
|
password: env.LANGFUSE_SECRET_KEY,
|
|
268
300
|
});
|
|
269
|
-
return client
|
|
301
|
+
return client;
|
|
270
302
|
}
|
|
271
303
|
catch (error) {
|
|
272
304
|
warn(error, { stage: "score_init" });
|
|
@@ -281,15 +313,31 @@ function createApiScorer(env, warn) {
|
|
|
281
313
|
const api = await init();
|
|
282
314
|
if (!api)
|
|
283
315
|
return false;
|
|
284
|
-
|
|
285
|
-
id: body.id,
|
|
316
|
+
const payload = {
|
|
317
|
+
...(body.id ? { id: body.id } : {}),
|
|
286
318
|
traceId: body.traceId,
|
|
319
|
+
...(body.observationId ? { observationId: body.observationId } : {}),
|
|
287
320
|
name: body.name,
|
|
288
321
|
value: body.value,
|
|
289
|
-
dataType: body.dataType,
|
|
290
|
-
|
|
322
|
+
...(body.dataType ? { dataType: body.dataType } : {}),
|
|
323
|
+
...(typeof body.comment === "string" ? { comment: boundComment(body.comment) } : {}),
|
|
291
324
|
environment: resolveLlmTracingEnvironment(env),
|
|
292
|
-
}
|
|
325
|
+
};
|
|
326
|
+
const requestOptions = { timeoutInSeconds: 5, maxRetries: 0 };
|
|
327
|
+
if (body.timestamp) {
|
|
328
|
+
// POST /scores cannot carry a timestamp. Score ingestion remains
|
|
329
|
+
// supported in v4 (unlike trace/observation ingestion) and preserves
|
|
330
|
+
// the date component of the score's deduplication identity.
|
|
331
|
+
if (!body.id || !Number.isFinite(body.timestamp.getTime()))
|
|
332
|
+
return false;
|
|
333
|
+
const eventId = randomUUID();
|
|
334
|
+
const result = await api.ingestion.batch({ batch: [{
|
|
335
|
+
type: "score-create", id: eventId, timestamp: body.timestamp.toISOString(), body: payload,
|
|
336
|
+
}] }, requestOptions);
|
|
337
|
+
// A 207 is transport success even if the event was rejected.
|
|
338
|
+
return result.errors.length === 0 && result.successes.some((event) => event.id === eventId && event.status >= 200 && event.status < 300);
|
|
339
|
+
}
|
|
340
|
+
await api.scores.create(payload, requestOptions);
|
|
293
341
|
return true;
|
|
294
342
|
}
|
|
295
343
|
catch (error) {
|
|
@@ -398,6 +446,7 @@ export function createLlmTracingClient(env = process.env, options) {
|
|
|
398
446
|
emitter.emit({
|
|
399
447
|
kind,
|
|
400
448
|
traceSeed: body.traceId,
|
|
449
|
+
...(body.observationId ? { observationId: body.observationId } : {}),
|
|
401
450
|
name: body.name,
|
|
402
451
|
attributes: compact({
|
|
403
452
|
input: body.input,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The country vocabulary behind the CRM's "Primary Location — Country" column.
|
|
3
|
+
*
|
|
4
|
+
* WHY A CHECKED-IN TABLE AND NOT `Intl.DisplayNames` AT RUNTIME: these strings
|
|
5
|
+
* are persisted. They become `definition.options[].value` on the column and the
|
|
6
|
+
* literal text stored in every cell, so deriving them from ICU would let a Node
|
|
7
|
+
* upgrade silently rename an option and orphan every row that already carried
|
|
8
|
+
* the old spelling. The table was GENERATED from CLDR English region names once
|
|
9
|
+
* (the 249 officially assigned ISO 3166-1 alpha-2 codes, plus XK/Kosovo, which
|
|
10
|
+
* LinkedIn lists and ISO does not), then frozen here with the overrides below.
|
|
11
|
+
*
|
|
12
|
+
* NAMING: LinkedIn spells a handful differently from CLDR's English short name
|
|
13
|
+
* — those are the entries carrying a trailing comment. Everything else is the
|
|
14
|
+
* CLDR name verbatim, which is what LinkedIn uses. This is a faithful
|
|
15
|
+
* reproduction of LinkedIn's vocabulary, not a scrape of its dropdown: the
|
|
16
|
+
* RESOLVER below is what makes that safe, because it accepts the alpha-2 code,
|
|
17
|
+
* any spelling in this table, and the aliases in COUNTRY_ALIASES. A scrape that
|
|
18
|
+
* returns "US", "United States", "USA" or "Congo - Kinshasa" all land on the
|
|
19
|
+
* one canonical option rather than on an empty cell.
|
|
20
|
+
*/
|
|
21
|
+
/** Canonical LinkedIn country name for each ISO 3166-1 alpha-2 code, A→Z by name. */
|
|
22
|
+
export declare const LINKEDIN_COUNTRIES: ReadonlyArray<readonly [code: string, name: string]>;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a provider value — an alpha-2 code, a canonical name, or a known
|
|
25
|
+
* alias — to its canonical LinkedIn country name.
|
|
26
|
+
*
|
|
27
|
+
* Returns null for anything unrecognized, which is deliberate: the column is a
|
|
28
|
+
* select, so writing a value that is not one of its options would produce a
|
|
29
|
+
* cell no filter can match and no dropdown can show. An empty cell is the
|
|
30
|
+
* honest outcome, and the raw payload is still on the record.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveLinkedInCountry(value: unknown): string | null;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The country vocabulary behind the CRM's "Primary Location — Country" column.
|
|
3
|
+
*
|
|
4
|
+
* WHY A CHECKED-IN TABLE AND NOT `Intl.DisplayNames` AT RUNTIME: these strings
|
|
5
|
+
* are persisted. They become `definition.options[].value` on the column and the
|
|
6
|
+
* literal text stored in every cell, so deriving them from ICU would let a Node
|
|
7
|
+
* upgrade silently rename an option and orphan every row that already carried
|
|
8
|
+
* the old spelling. The table was GENERATED from CLDR English region names once
|
|
9
|
+
* (the 249 officially assigned ISO 3166-1 alpha-2 codes, plus XK/Kosovo, which
|
|
10
|
+
* LinkedIn lists and ISO does not), then frozen here with the overrides below.
|
|
11
|
+
*
|
|
12
|
+
* NAMING: LinkedIn spells a handful differently from CLDR's English short name
|
|
13
|
+
* — those are the entries carrying a trailing comment. Everything else is the
|
|
14
|
+
* CLDR name verbatim, which is what LinkedIn uses. This is a faithful
|
|
15
|
+
* reproduction of LinkedIn's vocabulary, not a scrape of its dropdown: the
|
|
16
|
+
* RESOLVER below is what makes that safe, because it accepts the alpha-2 code,
|
|
17
|
+
* any spelling in this table, and the aliases in COUNTRY_ALIASES. A scrape that
|
|
18
|
+
* returns "US", "United States", "USA" or "Congo - Kinshasa" all land on the
|
|
19
|
+
* one canonical option rather than on an empty cell.
|
|
20
|
+
*/
|
|
21
|
+
/** Canonical LinkedIn country name for each ISO 3166-1 alpha-2 code, A→Z by name. */
|
|
22
|
+
export const LINKEDIN_COUNTRIES = [
|
|
23
|
+
["AF", "Afghanistan"],
|
|
24
|
+
["AX", "Åland Islands"],
|
|
25
|
+
["AL", "Albania"],
|
|
26
|
+
["DZ", "Algeria"],
|
|
27
|
+
["AS", "American Samoa"],
|
|
28
|
+
["AD", "Andorra"],
|
|
29
|
+
["AO", "Angola"],
|
|
30
|
+
["AI", "Anguilla"],
|
|
31
|
+
["AQ", "Antarctica"],
|
|
32
|
+
["AG", "Antigua & Barbuda"],
|
|
33
|
+
["AR", "Argentina"],
|
|
34
|
+
["AM", "Armenia"],
|
|
35
|
+
["AW", "Aruba"],
|
|
36
|
+
["AU", "Australia"],
|
|
37
|
+
["AT", "Austria"],
|
|
38
|
+
["AZ", "Azerbaijan"],
|
|
39
|
+
["BS", "Bahamas"],
|
|
40
|
+
["BH", "Bahrain"],
|
|
41
|
+
["BD", "Bangladesh"],
|
|
42
|
+
["BB", "Barbados"],
|
|
43
|
+
["BY", "Belarus"],
|
|
44
|
+
["BE", "Belgium"],
|
|
45
|
+
["BZ", "Belize"],
|
|
46
|
+
["BJ", "Benin"],
|
|
47
|
+
["BM", "Bermuda"],
|
|
48
|
+
["BT", "Bhutan"],
|
|
49
|
+
["BO", "Bolivia"],
|
|
50
|
+
["BA", "Bosnia & Herzegovina"],
|
|
51
|
+
["BW", "Botswana"],
|
|
52
|
+
["BV", "Bouvet Island"],
|
|
53
|
+
["BR", "Brazil"],
|
|
54
|
+
["IO", "British Indian Ocean Territory"],
|
|
55
|
+
["VG", "British Virgin Islands"],
|
|
56
|
+
["BN", "Brunei"],
|
|
57
|
+
["BG", "Bulgaria"],
|
|
58
|
+
["BF", "Burkina Faso"],
|
|
59
|
+
["BI", "Burundi"],
|
|
60
|
+
["KH", "Cambodia"],
|
|
61
|
+
["CM", "Cameroon"],
|
|
62
|
+
["CA", "Canada"],
|
|
63
|
+
["CV", "Cape Verde"],
|
|
64
|
+
["BQ", "Caribbean Netherlands"],
|
|
65
|
+
["KY", "Cayman Islands"],
|
|
66
|
+
["CF", "Central African Republic"],
|
|
67
|
+
["TD", "Chad"],
|
|
68
|
+
["CL", "Chile"],
|
|
69
|
+
["CN", "China"],
|
|
70
|
+
["CX", "Christmas Island"],
|
|
71
|
+
["CC", "Cocos (Keeling) Islands"],
|
|
72
|
+
["CO", "Colombia"],
|
|
73
|
+
["KM", "Comoros"],
|
|
74
|
+
["CD", "Congo (DRC)"],
|
|
75
|
+
["CG", "Congo (Republic)"],
|
|
76
|
+
["CK", "Cook Islands"],
|
|
77
|
+
["CR", "Costa Rica"],
|
|
78
|
+
["CI", "Côte d'Ivoire"],
|
|
79
|
+
["HR", "Croatia"],
|
|
80
|
+
["CU", "Cuba"],
|
|
81
|
+
["CW", "Curaçao"],
|
|
82
|
+
["CY", "Cyprus"],
|
|
83
|
+
["CZ", "Czechia"],
|
|
84
|
+
["DK", "Denmark"],
|
|
85
|
+
["DJ", "Djibouti"],
|
|
86
|
+
["DM", "Dominica"],
|
|
87
|
+
["DO", "Dominican Republic"],
|
|
88
|
+
["EC", "Ecuador"],
|
|
89
|
+
["EG", "Egypt"],
|
|
90
|
+
["SV", "El Salvador"],
|
|
91
|
+
["GQ", "Equatorial Guinea"],
|
|
92
|
+
["ER", "Eritrea"],
|
|
93
|
+
["EE", "Estonia"],
|
|
94
|
+
["SZ", "Eswatini"],
|
|
95
|
+
["ET", "Ethiopia"],
|
|
96
|
+
["FK", "Falkland Islands"],
|
|
97
|
+
["FO", "Faroe Islands"],
|
|
98
|
+
["FJ", "Fiji"],
|
|
99
|
+
["FI", "Finland"],
|
|
100
|
+
["FR", "France"],
|
|
101
|
+
["GF", "French Guiana"],
|
|
102
|
+
["PF", "French Polynesia"],
|
|
103
|
+
["TF", "French Southern Territories"],
|
|
104
|
+
["GA", "Gabon"],
|
|
105
|
+
["GM", "Gambia"],
|
|
106
|
+
["GE", "Georgia"],
|
|
107
|
+
["DE", "Germany"],
|
|
108
|
+
["GH", "Ghana"],
|
|
109
|
+
["GI", "Gibraltar"],
|
|
110
|
+
["GR", "Greece"],
|
|
111
|
+
["GL", "Greenland"],
|
|
112
|
+
["GD", "Grenada"],
|
|
113
|
+
["GP", "Guadeloupe"],
|
|
114
|
+
["GU", "Guam"],
|
|
115
|
+
["GT", "Guatemala"],
|
|
116
|
+
["GG", "Guernsey"],
|
|
117
|
+
["GN", "Guinea"],
|
|
118
|
+
["GW", "Guinea-Bissau"],
|
|
119
|
+
["GY", "Guyana"],
|
|
120
|
+
["HT", "Haiti"],
|
|
121
|
+
["HM", "Heard & McDonald Islands"],
|
|
122
|
+
["HN", "Honduras"],
|
|
123
|
+
["HK", "Hong Kong SAR"],
|
|
124
|
+
["HU", "Hungary"],
|
|
125
|
+
["IS", "Iceland"],
|
|
126
|
+
["IN", "India"],
|
|
127
|
+
["ID", "Indonesia"],
|
|
128
|
+
["IR", "Iran"],
|
|
129
|
+
["IQ", "Iraq"],
|
|
130
|
+
["IE", "Ireland"],
|
|
131
|
+
["IM", "Isle of Man"],
|
|
132
|
+
["IL", "Israel"],
|
|
133
|
+
["IT", "Italy"],
|
|
134
|
+
["JM", "Jamaica"],
|
|
135
|
+
["JP", "Japan"],
|
|
136
|
+
["JE", "Jersey"],
|
|
137
|
+
["JO", "Jordan"],
|
|
138
|
+
["KZ", "Kazakhstan"],
|
|
139
|
+
["KE", "Kenya"],
|
|
140
|
+
["KI", "Kiribati"],
|
|
141
|
+
["XK", "Kosovo"],
|
|
142
|
+
["KW", "Kuwait"],
|
|
143
|
+
["KG", "Kyrgyzstan"],
|
|
144
|
+
["LA", "Laos"],
|
|
145
|
+
["LV", "Latvia"],
|
|
146
|
+
["LB", "Lebanon"],
|
|
147
|
+
["LS", "Lesotho"],
|
|
148
|
+
["LR", "Liberia"],
|
|
149
|
+
["LY", "Libya"],
|
|
150
|
+
["LI", "Liechtenstein"],
|
|
151
|
+
["LT", "Lithuania"],
|
|
152
|
+
["LU", "Luxembourg"],
|
|
153
|
+
["MO", "Macao SAR"],
|
|
154
|
+
["MG", "Madagascar"],
|
|
155
|
+
["MW", "Malawi"],
|
|
156
|
+
["MY", "Malaysia"],
|
|
157
|
+
["MV", "Maldives"],
|
|
158
|
+
["ML", "Mali"],
|
|
159
|
+
["MT", "Malta"],
|
|
160
|
+
["MH", "Marshall Islands"],
|
|
161
|
+
["MQ", "Martinique"],
|
|
162
|
+
["MR", "Mauritania"],
|
|
163
|
+
["MU", "Mauritius"],
|
|
164
|
+
["YT", "Mayotte"],
|
|
165
|
+
["MX", "Mexico"],
|
|
166
|
+
["FM", "Micronesia"],
|
|
167
|
+
["MD", "Moldova"],
|
|
168
|
+
["MC", "Monaco"],
|
|
169
|
+
["MN", "Mongolia"],
|
|
170
|
+
["ME", "Montenegro"],
|
|
171
|
+
["MS", "Montserrat"],
|
|
172
|
+
["MA", "Morocco"],
|
|
173
|
+
["MZ", "Mozambique"],
|
|
174
|
+
["MM", "Myanmar"],
|
|
175
|
+
["NA", "Namibia"],
|
|
176
|
+
["NR", "Nauru"],
|
|
177
|
+
["NP", "Nepal"],
|
|
178
|
+
["NL", "Netherlands"],
|
|
179
|
+
["NC", "New Caledonia"],
|
|
180
|
+
["NZ", "New Zealand"],
|
|
181
|
+
["NI", "Nicaragua"],
|
|
182
|
+
["NE", "Niger"],
|
|
183
|
+
["NG", "Nigeria"],
|
|
184
|
+
["NU", "Niue"],
|
|
185
|
+
["NF", "Norfolk Island"],
|
|
186
|
+
["KP", "North Korea"],
|
|
187
|
+
["MK", "North Macedonia"],
|
|
188
|
+
["MP", "Northern Mariana Islands"],
|
|
189
|
+
["NO", "Norway"],
|
|
190
|
+
["OM", "Oman"],
|
|
191
|
+
["PK", "Pakistan"],
|
|
192
|
+
["PW", "Palau"],
|
|
193
|
+
["PS", "Palestinian Territories"],
|
|
194
|
+
["PA", "Panama"],
|
|
195
|
+
["PG", "Papua New Guinea"],
|
|
196
|
+
["PY", "Paraguay"],
|
|
197
|
+
["PE", "Peru"],
|
|
198
|
+
["PH", "Philippines"],
|
|
199
|
+
["PN", "Pitcairn Islands"],
|
|
200
|
+
["PL", "Poland"],
|
|
201
|
+
["PT", "Portugal"],
|
|
202
|
+
["PR", "Puerto Rico"],
|
|
203
|
+
["QA", "Qatar"],
|
|
204
|
+
["RE", "Réunion"],
|
|
205
|
+
["RO", "Romania"],
|
|
206
|
+
["RU", "Russia"],
|
|
207
|
+
["RW", "Rwanda"],
|
|
208
|
+
["WS", "Samoa"],
|
|
209
|
+
["SM", "San Marino"],
|
|
210
|
+
["ST", "São Tomé & Príncipe"],
|
|
211
|
+
["SA", "Saudi Arabia"],
|
|
212
|
+
["SN", "Senegal"],
|
|
213
|
+
["RS", "Serbia"],
|
|
214
|
+
["SC", "Seychelles"],
|
|
215
|
+
["SL", "Sierra Leone"],
|
|
216
|
+
["SG", "Singapore"],
|
|
217
|
+
["SX", "Sint Maarten"],
|
|
218
|
+
["SK", "Slovakia"],
|
|
219
|
+
["SI", "Slovenia"],
|
|
220
|
+
["SB", "Solomon Islands"],
|
|
221
|
+
["SO", "Somalia"],
|
|
222
|
+
["ZA", "South Africa"],
|
|
223
|
+
["GS", "South Georgia & South Sandwich Islands"],
|
|
224
|
+
["KR", "South Korea"],
|
|
225
|
+
["SS", "South Sudan"],
|
|
226
|
+
["ES", "Spain"],
|
|
227
|
+
["LK", "Sri Lanka"],
|
|
228
|
+
["BL", "St. Barthélemy"],
|
|
229
|
+
["SH", "St. Helena"],
|
|
230
|
+
["KN", "St. Kitts & Nevis"],
|
|
231
|
+
["LC", "St. Lucia"],
|
|
232
|
+
["MF", "St. Martin"],
|
|
233
|
+
["PM", "St. Pierre & Miquelon"],
|
|
234
|
+
["VC", "St. Vincent & Grenadines"],
|
|
235
|
+
["SD", "Sudan"],
|
|
236
|
+
["SR", "Suriname"],
|
|
237
|
+
["SJ", "Svalbard & Jan Mayen"],
|
|
238
|
+
["SE", "Sweden"],
|
|
239
|
+
["CH", "Switzerland"],
|
|
240
|
+
["SY", "Syria"],
|
|
241
|
+
["TW", "Taiwan"],
|
|
242
|
+
["TJ", "Tajikistan"],
|
|
243
|
+
["TZ", "Tanzania"],
|
|
244
|
+
["TH", "Thailand"],
|
|
245
|
+
["TL", "Timor-Leste"],
|
|
246
|
+
["TG", "Togo"],
|
|
247
|
+
["TK", "Tokelau"],
|
|
248
|
+
["TO", "Tonga"],
|
|
249
|
+
["TT", "Trinidad & Tobago"],
|
|
250
|
+
["TN", "Tunisia"],
|
|
251
|
+
["TR", "Türkiye"],
|
|
252
|
+
["TM", "Turkmenistan"],
|
|
253
|
+
["TC", "Turks & Caicos Islands"],
|
|
254
|
+
["TV", "Tuvalu"],
|
|
255
|
+
["UM", "U.S. Outlying Islands"],
|
|
256
|
+
["VI", "U.S. Virgin Islands"],
|
|
257
|
+
["UG", "Uganda"],
|
|
258
|
+
["UA", "Ukraine"],
|
|
259
|
+
["AE", "United Arab Emirates"],
|
|
260
|
+
["GB", "United Kingdom"],
|
|
261
|
+
["US", "United States"],
|
|
262
|
+
["UY", "Uruguay"],
|
|
263
|
+
["UZ", "Uzbekistan"],
|
|
264
|
+
["VU", "Vanuatu"],
|
|
265
|
+
["VA", "Vatican City"],
|
|
266
|
+
["VE", "Venezuela"],
|
|
267
|
+
["VN", "Vietnam"],
|
|
268
|
+
["WF", "Wallis & Futuna"],
|
|
269
|
+
["EH", "Western Sahara"],
|
|
270
|
+
["YE", "Yemen"],
|
|
271
|
+
["ZM", "Zambia"],
|
|
272
|
+
["ZW", "Zimbabwe"],
|
|
273
|
+
];
|
|
274
|
+
/**
|
|
275
|
+
* Spellings a provider may return that are not the canonical name. Lowercased
|
|
276
|
+
* keys; the resolver lowercases its input before the lookup. Kept deliberately
|
|
277
|
+
* short — it covers the CLDR spellings this table overrides (so a payload
|
|
278
|
+
* carrying the un-overridden name still resolves), the common colloquial forms,
|
|
279
|
+
* and the alpha-3 codes, because Up2Data's `hq.country` is not contractually
|
|
280
|
+
* alpha-2 and has been observed carrying a display name.
|
|
281
|
+
*/
|
|
282
|
+
const COUNTRY_ALIASES = {
|
|
283
|
+
// CLDR spellings this table overrides.
|
|
284
|
+
"hong kong sar china": "HK",
|
|
285
|
+
"macao sar china": "MO",
|
|
286
|
+
"macau sar china": "MO",
|
|
287
|
+
"myanmar (burma)": "MM",
|
|
288
|
+
"congo - kinshasa": "CD",
|
|
289
|
+
"congo - brazzaville": "CG",
|
|
290
|
+
"côte d’ivoire": "CI",
|
|
291
|
+
"st. helena": "SH",
|
|
292
|
+
// Colloquial and legacy forms.
|
|
293
|
+
usa: "US",
|
|
294
|
+
"u.s.": "US",
|
|
295
|
+
"u.s.a.": "US",
|
|
296
|
+
"united states of america": "US",
|
|
297
|
+
uk: "GB",
|
|
298
|
+
"u.k.": "GB",
|
|
299
|
+
"great britain": "GB",
|
|
300
|
+
england: "GB",
|
|
301
|
+
scotland: "GB",
|
|
302
|
+
wales: "GB",
|
|
303
|
+
"northern ireland": "GB",
|
|
304
|
+
"czech republic": "CZ",
|
|
305
|
+
turkey: "TR",
|
|
306
|
+
"ivory coast": "CI",
|
|
307
|
+
burma: "MM",
|
|
308
|
+
swaziland: "SZ",
|
|
309
|
+
macedonia: "MK",
|
|
310
|
+
holland: "NL",
|
|
311
|
+
"the netherlands": "NL",
|
|
312
|
+
"south korea": "KR",
|
|
313
|
+
"korea, republic of": "KR",
|
|
314
|
+
"republic of korea": "KR",
|
|
315
|
+
"north korea": "KP",
|
|
316
|
+
"russian federation": "RU",
|
|
317
|
+
vatican: "VA",
|
|
318
|
+
"holy see": "VA",
|
|
319
|
+
palestine: "PS",
|
|
320
|
+
laos: "LA",
|
|
321
|
+
"cape verde": "CV",
|
|
322
|
+
"east timor": "TL",
|
|
323
|
+
uae: "AE",
|
|
324
|
+
"democratic republic of the congo": "CD",
|
|
325
|
+
"republic of the congo": "CG",
|
|
326
|
+
"hong kong": "HK",
|
|
327
|
+
macao: "MO",
|
|
328
|
+
macau: "MO",
|
|
329
|
+
};
|
|
330
|
+
const BY_CODE = new Map(LINKEDIN_COUNTRIES.map(([code, name]) => [code, name]));
|
|
331
|
+
const BY_LOWER_NAME = new Map(LINKEDIN_COUNTRIES.map(([code, name]) => [name.toLowerCase(), code]));
|
|
332
|
+
/**
|
|
333
|
+
* Resolve a provider value — an alpha-2 code, a canonical name, or a known
|
|
334
|
+
* alias — to its canonical LinkedIn country name.
|
|
335
|
+
*
|
|
336
|
+
* Returns null for anything unrecognized, which is deliberate: the column is a
|
|
337
|
+
* select, so writing a value that is not one of its options would produce a
|
|
338
|
+
* cell no filter can match and no dropdown can show. An empty cell is the
|
|
339
|
+
* honest outcome, and the raw payload is still on the record.
|
|
340
|
+
*/
|
|
341
|
+
export function resolveLinkedInCountry(value) {
|
|
342
|
+
if (typeof value !== "string")
|
|
343
|
+
return null;
|
|
344
|
+
const trimmed = value.trim();
|
|
345
|
+
if (!trimmed)
|
|
346
|
+
return null;
|
|
347
|
+
const upper = trimmed.toUpperCase();
|
|
348
|
+
if (upper.length === 2) {
|
|
349
|
+
const direct = BY_CODE.get(upper);
|
|
350
|
+
if (direct)
|
|
351
|
+
return direct;
|
|
352
|
+
}
|
|
353
|
+
const lower = trimmed.toLowerCase();
|
|
354
|
+
const byName = BY_LOWER_NAME.get(lower);
|
|
355
|
+
if (byName)
|
|
356
|
+
return BY_CODE.get(byName) ?? null;
|
|
357
|
+
const aliased = COUNTRY_ALIASES[lower];
|
|
358
|
+
return aliased ? BY_CODE.get(aliased) ?? null : null;
|
|
359
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { LogSink } from "./log.js";
|
|
2
|
+
/**
|
|
3
|
+
* Which transport(s) `log()` records leave the process on.
|
|
4
|
+
*
|
|
5
|
+
* Portability selector (`.agents/skills/oxygen-platform-portability/SKILL.md`):
|
|
6
|
+
* one concern, one variable, and **unset means managed**. An environment that
|
|
7
|
+
* sets nothing keeps today's behaviour exactly — the Axiom shipper alone, with
|
|
8
|
+
* the same handle and the same drain — so this can land on `dev` and flow to
|
|
9
|
+
* managed production with no flag day. Self-hosted opts in with `otlp` (an
|
|
10
|
+
* OTLP/HTTP collector instead of Axiom) or `both` (during a cutover window,
|
|
11
|
+
* when the two stores must be comparable before Axiom is switched off).
|
|
12
|
+
*
|
|
13
|
+
* An unrecognised value resolves to `axiom` rather than throwing: a typo in a
|
|
14
|
+
* telemetry variable must not decide whether a process boots.
|
|
15
|
+
*/
|
|
16
|
+
export type LogSinkSelector = "axiom" | "otlp" | "both";
|
|
17
|
+
export declare function resolveLogSinkSelector(env?: NodeJS.ProcessEnv): LogSinkSelector;
|
|
18
|
+
export declare function logSinkSelectorIncludesAxiom(selector: LogSinkSelector): boolean;
|
|
19
|
+
export declare function logSinkSelectorIncludesOtlp(selector: LogSinkSelector): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Failed sink invocations swallowed by a composed sink since process start.
|
|
22
|
+
*
|
|
23
|
+
* A composed sink cannot report its own failure through `log()` — it IS what
|
|
24
|
+
* `log()` calls, so logging from it would recurse. The count is the honest
|
|
25
|
+
* alternative to silence: readable from a diagnostic surface without the
|
|
26
|
+
* failing transport ever getting a second chance to throw.
|
|
27
|
+
*/
|
|
28
|
+
export declare function composedLogSinkFailureCount(): number;
|
|
29
|
+
/**
|
|
30
|
+
* Fans one record out to several transports.
|
|
31
|
+
*
|
|
32
|
+
* Every sink is called, in order, and each is wrapped: `both` exists precisely
|
|
33
|
+
* for the window where one of the two stores is not trusted yet, so a throwing
|
|
34
|
+
* Axiom sink must not stop the OTLP one from receiving the record (or the
|
|
35
|
+
* reverse). Nulls are dropped, so a caller can pass an adapter that declined to
|
|
36
|
+
* start without branching; all-null composes to `null`, which `setLogSink`
|
|
37
|
+
* reads as "no sink" and which keeps `log()` on its plain stdout path.
|
|
38
|
+
*/
|
|
39
|
+
export declare function composeLogSinks(...sinks: Array<LogSink | null | undefined>): LogSink | null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function resolveLogSinkSelector(env = process.env) {
|
|
2
|
+
const configured = env.OXYGEN_LOG_SINK?.trim().toLowerCase();
|
|
3
|
+
if (configured === "otlp")
|
|
4
|
+
return "otlp";
|
|
5
|
+
if (configured === "both")
|
|
6
|
+
return "both";
|
|
7
|
+
return "axiom";
|
|
8
|
+
}
|
|
9
|
+
export function logSinkSelectorIncludesAxiom(selector) {
|
|
10
|
+
return selector === "axiom" || selector === "both";
|
|
11
|
+
}
|
|
12
|
+
export function logSinkSelectorIncludesOtlp(selector) {
|
|
13
|
+
return selector === "otlp" || selector === "both";
|
|
14
|
+
}
|
|
15
|
+
let composedSinkFailures = 0;
|
|
16
|
+
/**
|
|
17
|
+
* Failed sink invocations swallowed by a composed sink since process start.
|
|
18
|
+
*
|
|
19
|
+
* A composed sink cannot report its own failure through `log()` — it IS what
|
|
20
|
+
* `log()` calls, so logging from it would recurse. The count is the honest
|
|
21
|
+
* alternative to silence: readable from a diagnostic surface without the
|
|
22
|
+
* failing transport ever getting a second chance to throw.
|
|
23
|
+
*/
|
|
24
|
+
export function composedLogSinkFailureCount() {
|
|
25
|
+
return composedSinkFailures;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fans one record out to several transports.
|
|
29
|
+
*
|
|
30
|
+
* Every sink is called, in order, and each is wrapped: `both` exists precisely
|
|
31
|
+
* for the window where one of the two stores is not trusted yet, so a throwing
|
|
32
|
+
* Axiom sink must not stop the OTLP one from receiving the record (or the
|
|
33
|
+
* reverse). Nulls are dropped, so a caller can pass an adapter that declined to
|
|
34
|
+
* start without branching; all-null composes to `null`, which `setLogSink`
|
|
35
|
+
* reads as "no sink" and which keeps `log()` on its plain stdout path.
|
|
36
|
+
*/
|
|
37
|
+
export function composeLogSinks(...sinks) {
|
|
38
|
+
const active = sinks.filter((sink) => typeof sink === "function");
|
|
39
|
+
if (active.length === 0)
|
|
40
|
+
return null;
|
|
41
|
+
const only = active[0];
|
|
42
|
+
if (active.length === 1 && only)
|
|
43
|
+
return only;
|
|
44
|
+
return (record) => {
|
|
45
|
+
for (const sink of active) {
|
|
46
|
+
try {
|
|
47
|
+
sink(record);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Telemetry must never break the code it observes, and this is the one
|
|
51
|
+
// place that cannot say so out loud (see composedLogSinkFailureCount).
|
|
52
|
+
composedSinkFailures += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -19,6 +19,7 @@ export declare function enterLogContext(ctx: LogContext): void;
|
|
|
19
19
|
export declare function logLevelForHttpStatus(status: number): LogLevel;
|
|
20
20
|
export type LogSink = (record: Record<string, unknown>) => void;
|
|
21
21
|
export declare function setLogSink(sink: LogSink | null): void;
|
|
22
|
+
export declare function getLogSink(): LogSink | null;
|
|
22
23
|
export declare function log(level: LogLevel, msg: string, fields?: Record<string, unknown>): void;
|
|
23
24
|
export declare function errorId(err: unknown): string;
|
|
24
25
|
export declare function errorFields(err: unknown): Record<string, unknown>;
|
|
@@ -118,7 +118,12 @@ const LOG_SINK_SLOT = Symbol.for("oxygen.log.sink");
|
|
|
118
118
|
export function setLogSink(sink) {
|
|
119
119
|
globalThis[LOG_SINK_SLOT] = sink;
|
|
120
120
|
}
|
|
121
|
-
|
|
121
|
+
// Exported so a second transport can COMPOSE with the sink already registered
|
|
122
|
+
// rather than replacing it: under `OXYGEN_LOG_SINK=both` the OTLP adapter starts
|
|
123
|
+
// after the Axiom shipper has claimed the slot, and reading the slot back is what
|
|
124
|
+
// lets it fan out to both (`log-sink-selector.ts`). Reading through this function,
|
|
125
|
+
// not the raw Symbol, keeps the slot's location owned by exactly one module.
|
|
126
|
+
export function getLogSink() {
|
|
122
127
|
return globalThis[LOG_SINK_SLOT] ?? null;
|
|
123
128
|
}
|
|
124
129
|
const LEVEL_RANK = { debug: 10, info: 20, warn: 30, error: 40 };
|