@oxygen-agent/cli 1.1010.721 → 1.1010.905
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/auto-update.d.ts +129 -0
- package/dist/auto-update.js +392 -0
- package/dist/command-manifest.js +14 -0
- package/dist/credentials.d.ts +2 -0
- package/dist/credentials.js +6 -3
- package/dist/functions-commands.js +1 -1
- package/dist/http-client.js +28 -4
- package/dist/index.js +583 -145
- package/dist/run-wait.d.ts +3 -1
- package/dist/run-wait.js +19 -5
- package/dist/streamed-file-import.d.ts +58 -0
- package/dist/streamed-file-import.js +115 -0
- package/dist/update.d.ts +29 -0
- package/dist/update.js +62 -16
- package/dist/workflow-plan-limit-notices.d.ts +8 -0
- package/dist/workflow-plan-limit-notices.js +28 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +3 -3
- package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.js +27 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +191 -35
- package/node_modules/@oxygen/shared/dist/billing.js +333 -42
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +55 -5
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +2 -2
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +90 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +167 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate.js +361 -0
- package/node_modules/@oxygen/shared/dist/credit-gate.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/credit-gate.js +65 -0
- package/node_modules/@oxygen/shared/dist/email-deliverability-policy.d.ts +51 -0
- package/node_modules/@oxygen/shared/dist/email-deliverability-policy.js +101 -0
- package/node_modules/@oxygen/shared/dist/email-hard-bounce.d.ts +3 -1
- package/node_modules/@oxygen/shared/dist/email-hard-bounce.js +3 -3
- package/node_modules/@oxygen/shared/dist/error-redaction.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -1
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +5 -0
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +13 -1
- package/node_modules/@oxygen/shared/dist/file-import.js +33 -6
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +73 -3
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +246 -24
- package/node_modules/@oxygen/shared/dist/import-limits.d.ts +25 -1
- package/node_modules/@oxygen/shared/dist/import-limits.js +35 -2
- package/node_modules/@oxygen/shared/dist/index.d.ts +2 -22
- package/node_modules/@oxygen/shared/dist/index.js +2 -42
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +17 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +55 -0
- package/node_modules/@oxygen/shared/dist/plan-band.d.ts +117 -1
- package/node_modules/@oxygen/shared/dist/plan-band.js +175 -10
- package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +77 -7
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +87 -7
- package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +219 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.js +330 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +204 -6
- package/node_modules/@oxygen/shared/dist/plan-limits.js +197 -15
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +80 -36
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +80 -31
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +38 -20
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +47 -34
- package/node_modules/@oxygen/shared/dist/provider-http-error.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/provider-http-error.js +27 -0
- package/node_modules/@oxygen/shared/dist/repricing.d.ts +127 -0
- package/node_modules/@oxygen/shared/dist/repricing.js +407 -6
- package/node_modules/@oxygen/shared/dist/semver.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/semver.js +41 -0
- package/node_modules/@oxygen/shared/dist/sending-limits.d.ts +5 -7
- package/node_modules/@oxygen/shared/dist/sending-limits.js +10 -16
- package/node_modules/@oxygen/shared/dist/sending-seats.d.ts +18 -15
- package/node_modules/@oxygen/shared/dist/sending-seats.js +22 -17
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +57 -8
- package/node_modules/@oxygen/shared/dist/spend-safety.js +64 -11
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +15 -7
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +18 -5
- package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +34 -5
- package/node_modules/@oxygen/shared/dist/table-capacity.js +25 -8
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +13 -5
- package/node_modules/@oxygen/shared/dist/telemetry-resource.d.ts +40 -0
- package/node_modules/@oxygen/shared/dist/telemetry-resource.js +35 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +5 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/ugc.js +29 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -3
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +14 -27
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +5 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +3 -3
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +15 -1
- package/node_modules/@oxygen/workflows/dist/graph/types.js +15 -1
- package/node_modules/@oxygen/workflows/dist/index.d.ts +45 -0
- package/node_modules/@oxygen/workflows/dist/index.js +152 -2
- package/node_modules/@oxygen/workflows/dist/usage-estimate.d.ts +10 -1
- package/node_modules/@oxygen/workflows/dist/usage-estimate.js +33 -29
- package/package.json +1 -1
|
@@ -76,6 +76,10 @@ export type FeatureGateResolution = {
|
|
|
76
76
|
* (`packages/shared/src/repricing.ts`). A remotely flippable value would let
|
|
77
77
|
* a mis-click move every customer's prices; it changes only through Doppler,
|
|
78
78
|
* with explicit human approval.
|
|
79
|
+
* - OXYGEN_CREDIT_GATE_ENABLED — (b). Lets a connect through on the balance
|
|
80
|
+
* instead of a sending seat (`packages/shared/src/credit-gate.ts`). A wrong
|
|
81
|
+
* "on" while commitments do not bill every connectable kind would admit
|
|
82
|
+
* accounts nothing ever charges.
|
|
79
83
|
*
|
|
80
84
|
* The list is a guard, not a to-do: none of these is migrated, and
|
|
81
85
|
* `findFeatureGateRegistryViolations` fails the build's tests if one ever is.
|
|
@@ -67,6 +67,10 @@
|
|
|
67
67
|
* (`packages/shared/src/repricing.ts`). A remotely flippable value would let
|
|
68
68
|
* a mis-click move every customer's prices; it changes only through Doppler,
|
|
69
69
|
* with explicit human approval.
|
|
70
|
+
* - OXYGEN_CREDIT_GATE_ENABLED — (b). Lets a connect through on the balance
|
|
71
|
+
* instead of a sending seat (`packages/shared/src/credit-gate.ts`). A wrong
|
|
72
|
+
* "on" while commitments do not bill every connectable kind would admit
|
|
73
|
+
* accounts nothing ever charges.
|
|
70
74
|
*
|
|
71
75
|
* The list is a guard, not a to-do: none of these is migrated, and
|
|
72
76
|
* `findFeatureGateRegistryViolations` fails the build's tests if one ever is.
|
|
@@ -82,6 +86,7 @@ export const NEVER_FLAGGABLE = [
|
|
|
82
86
|
"OXYGEN_TRIGGER_DEFAULT_CAPS",
|
|
83
87
|
"OXYGEN_BYOK_DAILY_CAPS",
|
|
84
88
|
"OXYGEN_REPRICING_2026_09_EFFECTIVE_AT",
|
|
89
|
+
"OXYGEN_CREDIT_GATE_ENABLED",
|
|
85
90
|
];
|
|
86
91
|
/**
|
|
87
92
|
* Every server-side gate resolved through PostHog.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Row } from "read-excel-file/node";
|
|
2
2
|
import { type ImportColumnDataType } from "./column-types.js";
|
|
3
|
-
export { MAX_BUFFERED_IMPORT_PARSE_BYTES } from "./import-limits.js";
|
|
3
|
+
export { IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES, MAX_BUFFERED_IMPORT_PARSE_BYTES, MAX_URL_IMPORT_BYTES, bufferedImportTooLargeMessage, isBufferedOnlyImportFormat, } from "./import-limits.js";
|
|
4
4
|
export type RowsFileFormat = "json" | "jsonl" | "csv" | "xlsx";
|
|
5
5
|
export type ImportTableColumn = {
|
|
6
6
|
label: string;
|
|
@@ -68,3 +68,15 @@ export declare function parseRowsSampleText(text: string, format: Exclude<RowsFi
|
|
|
68
68
|
export declare function inferImportColumnLabels(rows: Record<string, unknown>[]): string[];
|
|
69
69
|
export declare function normalizeRowsForNewTable(rows: Record<string, unknown>[]): NewTableImportRows;
|
|
70
70
|
export declare function normalizeImportColumnKey(value: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Refuse a JSON or XLSX file over the buffered-parse ceiling before anything is
|
|
73
|
+
* uploaded or queued. CSV and JSONL stream, so they pass at any size and only
|
|
74
|
+
* the plan's file limit applies to them.
|
|
75
|
+
*/
|
|
76
|
+
export declare function assertImportFormatWithinBufferedLimit(format: RowsFileFormat, fileBytes: number): void;
|
|
77
|
+
/**
|
|
78
|
+
* Refuse a file sent whole inside one request (the multipart fallback used when
|
|
79
|
+
* object storage is not configured) over the buffered ceiling: that path holds
|
|
80
|
+
* the file in the web process, whatever the plan's file limit says.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertInlineImportWithinLimit(fileBytes: number): void;
|
|
@@ -3,8 +3,8 @@ import readXlsxFile from "read-excel-file/node";
|
|
|
3
3
|
import { inferImportColumnDataType, parseDateValueToIso, } from "./column-types.js";
|
|
4
4
|
import { OxygenError } from "./cli-result.js";
|
|
5
5
|
import { makeUniqueIdentifier, toSnakeIdentifier } from "./identifiers.js";
|
|
6
|
-
import { MAX_BUFFERED_IMPORT_PARSE_BYTES } from "./import-limits.js";
|
|
7
|
-
export { MAX_BUFFERED_IMPORT_PARSE_BYTES } from "./import-limits.js";
|
|
6
|
+
import { MAX_BUFFERED_IMPORT_PARSE_BYTES, bufferedImportTooLargeMessage, isBufferedOnlyImportFormat, } from "./import-limits.js";
|
|
7
|
+
export { IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES, MAX_BUFFERED_IMPORT_PARSE_BYTES, MAX_URL_IMPORT_BYTES, bufferedImportTooLargeMessage, isBufferedOnlyImportFormat, } from "./import-limits.js";
|
|
8
8
|
export function inferRowsFileFormat(path) {
|
|
9
9
|
const extension = extname(path).toLowerCase();
|
|
10
10
|
if (extension === ".jsonl" || extension === ".ndjson")
|
|
@@ -497,10 +497,11 @@ async function streamToLimitedBuffer(chunks, format) {
|
|
|
497
497
|
const buffer = Buffer.from(toUint8Array(chunk));
|
|
498
498
|
total += buffer.byteLength;
|
|
499
499
|
if (total > MAX_BUFFERED_IMPORT_PARSE_BYTES) {
|
|
500
|
-
throw new OxygenError("import_file_too_large_for_format",
|
|
500
|
+
throw new OxygenError("import_file_too_large_for_format", bufferedImportTooLargeMessage(format), {
|
|
501
501
|
details: {
|
|
502
502
|
format,
|
|
503
503
|
max_buffered_parse_bytes: MAX_BUFFERED_IMPORT_PARSE_BYTES,
|
|
504
|
+
next_step: "Save the file as CSV or JSONL and import it again.",
|
|
504
505
|
},
|
|
505
506
|
exitCode: 1,
|
|
506
507
|
});
|
|
@@ -523,13 +524,39 @@ function normalizeBatchSize(value) {
|
|
|
523
524
|
return Math.max(1, Math.trunc(value));
|
|
524
525
|
}
|
|
525
526
|
function assertBufferedParseWithinLimit(buffer, format) {
|
|
526
|
-
|
|
527
|
+
assertImportFormatWithinBufferedLimit(format, buffer.byteLength);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Refuse a JSON or XLSX file over the buffered-parse ceiling before anything is
|
|
531
|
+
* uploaded or queued. CSV and JSONL stream, so they pass at any size and only
|
|
532
|
+
* the plan's file limit applies to them.
|
|
533
|
+
*/
|
|
534
|
+
export function assertImportFormatWithinBufferedLimit(format, fileBytes) {
|
|
535
|
+
if (!isBufferedOnlyImportFormat(format) || fileBytes <= MAX_BUFFERED_IMPORT_PARSE_BYTES)
|
|
527
536
|
return;
|
|
528
|
-
throw new OxygenError("buffered_import_too_large",
|
|
537
|
+
throw new OxygenError("buffered_import_too_large", bufferedImportTooLargeMessage(format, fileBytes), {
|
|
529
538
|
details: {
|
|
530
539
|
format,
|
|
531
|
-
file_bytes:
|
|
540
|
+
file_bytes: fileBytes,
|
|
532
541
|
max_buffered_parse_bytes: MAX_BUFFERED_IMPORT_PARSE_BYTES,
|
|
542
|
+
next_step: "Save the file as CSV or JSONL and import it again.",
|
|
543
|
+
},
|
|
544
|
+
exitCode: 1,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Refuse a file sent whole inside one request (the multipart fallback used when
|
|
549
|
+
* object storage is not configured) over the buffered ceiling: that path holds
|
|
550
|
+
* the file in the web process, whatever the plan's file limit says.
|
|
551
|
+
*/
|
|
552
|
+
export function assertInlineImportWithinLimit(fileBytes) {
|
|
553
|
+
if (fileBytes <= MAX_BUFFERED_IMPORT_PARSE_BYTES)
|
|
554
|
+
return;
|
|
555
|
+
throw new OxygenError("request_too_large", "Files this large upload straight to file storage, which is not available for this request.", {
|
|
556
|
+
details: {
|
|
557
|
+
file_bytes: fileBytes,
|
|
558
|
+
max_inline_file_bytes: MAX_BUFFERED_IMPORT_PARSE_BYTES,
|
|
559
|
+
next_step: "Import it with `oxygen tables import --file <path>` or the web import, which stage large files in object storage.",
|
|
533
560
|
},
|
|
534
561
|
exitCode: 1,
|
|
535
562
|
});
|
|
@@ -36,7 +36,12 @@ export type CopilotAiLevel = HostedAiLevel | "auto";
|
|
|
36
36
|
export type HostedAiModelSpec = {
|
|
37
37
|
/** OpenRouter model id, e.g. "deepseek/deepseek-v4-flash". */
|
|
38
38
|
model: string;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* What a customer sees for this tier: always the Oxygen tier label, never the
|
|
41
|
+
* vendor model name ("DO not expose MODELs names to users", Philipp,
|
|
42
|
+
* 2026-09-26). Customer surfaces read this (e.g. the Copilot session's
|
|
43
|
+
* `model_display_name`); staff and logs read `model`.
|
|
44
|
+
*/
|
|
40
45
|
displayName: string;
|
|
41
46
|
/** Ordered fallback model ids tried when `model` is unavailable. */
|
|
42
47
|
fallbackModels: readonly string[];
|
|
@@ -54,12 +59,29 @@ export type HostedAiModelSpec = {
|
|
|
54
59
|
estCompletionUsdPerM: number;
|
|
55
60
|
/** Upper bound on completion tokens requested for this tier. */
|
|
56
61
|
maxOutputTokens: number;
|
|
62
|
+
/**
|
|
63
|
+
* Send `reasoning: {enabled: false}` on a managed call to this tier's default
|
|
64
|
+
* model. Only where cost and quality were MEASURED with reasoning off: it is
|
|
65
|
+
* what the tier's price is calibrated against (ai_column Balanced, 2026-09-26).
|
|
66
|
+
*/
|
|
67
|
+
disableReasoning?: boolean;
|
|
57
68
|
};
|
|
58
69
|
export declare const HOSTED_AI_MODEL_REGISTRY: {
|
|
59
70
|
ai_column: Record<HostedAiLevel, HostedAiModelSpec>;
|
|
60
71
|
copilot: Record<CopilotAiLevel, HostedAiModelSpec>;
|
|
61
72
|
agent: Record<HostedAiLevel, HostedAiModelSpec>;
|
|
62
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Managed AI-column model ids that USED to back a tier, mapped to the tier they
|
|
76
|
+
* billed at. A managed column saved with one of these pinned keeps validating
|
|
77
|
+
* (tenant-db `resolveManagedAiModelTier`) and runs on that tier's CURRENT model,
|
|
78
|
+
* so retiring a model never strands a saved column or keeps it on a model we no
|
|
79
|
+
* longer sell. A BYOK column keeps whatever it pinned: that is the customer's
|
|
80
|
+
* own key and choice. Retired 2026-09-26 (see the ai_column block above).
|
|
81
|
+
*/
|
|
82
|
+
export declare const LEGACY_MANAGED_AI_COLUMN_MODELS: Readonly<Record<string, HostedAiLevel>>;
|
|
83
|
+
/** The tier a retired managed AI-column model id billed at, or null. */
|
|
84
|
+
export declare function legacyManagedAiColumnLevel(model: string): HostedAiLevel | null;
|
|
63
85
|
/**
|
|
64
86
|
* What a MANAGED AI column's reasoning tier is called in front of a customer.
|
|
65
87
|
*
|
|
@@ -123,8 +145,8 @@ export declare const AGENT_DEFAULT_LEVEL: HostedAiLevel;
|
|
|
123
145
|
* Resolve the model spec for a hosted-AI use case + reasoning level.
|
|
124
146
|
*
|
|
125
147
|
* For `copilot`, Doppler overrides are applied here:
|
|
126
|
-
* - `OXYGEN_COPILOT_MODEL_{LOW,MEDIUM,HIGH}` replaces `.model`
|
|
127
|
-
*
|
|
148
|
+
* - `OXYGEN_COPILOT_MODEL_{LOW,MEDIUM,HIGH}` replaces `.model`; `.displayName`
|
|
149
|
+
* keeps the tier label, because a customer never sees a model id.
|
|
128
150
|
* - `OXYGEN_COPILOT_FALLBACK_{LOW,MEDIUM,HIGH}` replaces `.fallbackModels`
|
|
129
151
|
* from a comma-separated list.
|
|
130
152
|
*
|
|
@@ -146,6 +168,54 @@ export declare function resolveHostedAiModel(input: {
|
|
|
146
168
|
level: CopilotAiLevel;
|
|
147
169
|
env?: Record<string, string | undefined>;
|
|
148
170
|
}): HostedAiModelSpec;
|
|
171
|
+
/**
|
|
172
|
+
* The customer-facing label for a model id OXYGEN manages, or null when the id is
|
|
173
|
+
* not one of ours (a BYOK pin, a customer's own request). OpenRouter can answer
|
|
174
|
+
* with a dated or routed variant (`vendor/model-20260915`, `vendor/model:nitro`),
|
|
175
|
+
* so those match their base id.
|
|
176
|
+
*/
|
|
177
|
+
export declare function managedModelLabel(model: string, preferredUseCase?: HostedAiUseCase): string | null;
|
|
178
|
+
/**
|
|
179
|
+
* What an AI-column run's provenance records as its model: the tier label for
|
|
180
|
+
* a managed run, and the model itself only when the customer pinned it on
|
|
181
|
+
* their own key. A BYOK run on the tier default is still OXYGEN's pick.
|
|
182
|
+
*/
|
|
183
|
+
export declare function customerFacingAiColumnModel(input: {
|
|
184
|
+
model: string;
|
|
185
|
+
credentialMode: "managed" | "byok";
|
|
186
|
+
reasoningLevel: HostedAiLevel;
|
|
187
|
+
}): string;
|
|
188
|
+
/**
|
|
189
|
+
* The model OpenRouter reports it answered with, for the same provenance. A
|
|
190
|
+
* managed run may have been served by a fallback, so it reads as the tier; a
|
|
191
|
+
* customer's own pin (or its dated variant) stays theirs.
|
|
192
|
+
*/
|
|
193
|
+
export declare function customerFacingAiColumnResponseModel(input: {
|
|
194
|
+
model: string;
|
|
195
|
+
credentialMode: "managed" | "byok";
|
|
196
|
+
reasoningLevel: HostedAiLevel;
|
|
197
|
+
responseModel: string | null;
|
|
198
|
+
}): string | null;
|
|
199
|
+
/**
|
|
200
|
+
* An AI-column request summary as the customer reads it: its `model` is the
|
|
201
|
+
* tier (or their pin), and any other managed id inside is redacted too.
|
|
202
|
+
*/
|
|
203
|
+
export declare function customerFacingAiColumnRequest<T extends Record<string, unknown>>(summary: T, input: {
|
|
204
|
+
model: string;
|
|
205
|
+
credentialMode: "managed" | "byok";
|
|
206
|
+
reasoningLevel: HostedAiLevel;
|
|
207
|
+
}): T;
|
|
208
|
+
/**
|
|
209
|
+
* Replace every string in `value` that is a managed model id with its tier
|
|
210
|
+
* label. Walks objects and arrays; only a string that IS a model id is touched,
|
|
211
|
+
* so prose that merely mentions a model is left alone. `allow` lists ids the
|
|
212
|
+
* customer chose (a BYOK pin), which pass through untouched.
|
|
213
|
+
*/
|
|
214
|
+
export declare function redactManagedModelIds<T>(value: T, options?: {
|
|
215
|
+
allow?: readonly (string | null | undefined)[];
|
|
216
|
+
preferredUseCase?: HostedAiUseCase;
|
|
217
|
+
env?: Record<string, string | undefined>;
|
|
218
|
+
}): T;
|
|
149
219
|
/**
|
|
150
220
|
* Convenience map of each reasoning tier's default `.model` id for a use case.
|
|
151
221
|
* `@oxygen/integrations` and `@oxygen/tenant-db` re-source their AI-column
|
|
@@ -21,29 +21,87 @@
|
|
|
21
21
|
* can re-source the catalog without dragging in the shared barrel.
|
|
22
22
|
*/
|
|
23
23
|
export const HOSTED_AI_MODEL_REGISTRY = {
|
|
24
|
+
/**
|
|
25
|
+
* Managed AI-column tiers, re-picked 2026-09-26 on measured evidence
|
|
26
|
+
* (Philipp, 2026-09-26: Clay-level quality, faster, 30-50% cheaper than Clay,
|
|
27
|
+
* and at least 5x markup on AI and research columns -- with one explicit
|
|
28
|
+
* founder exception for Max, below).
|
|
29
|
+
*
|
|
30
|
+
* Quality: a blind bakeoff of 13 OpenRouter models x 60 synthetic rows on the
|
|
31
|
+
* four jobs AI columns do (classify, extract, copy, grounded research), every
|
|
32
|
+
* request built by the runner's own buildOpenRouterRequestBody; copy and
|
|
33
|
+
* research graded 1-5 by a blind opus judge (spend $1.74). The previous tiers
|
|
34
|
+
* (DeepSeek V4 Flash / V4 Pro / Kimi K2.6) placed 11th-13th of 13.
|
|
35
|
+
*
|
|
36
|
+
* Cost: a second, production-shaped calibration run (golden-prodshape.json:
|
|
37
|
+
* research with 6-10 sources totalling ~6-20k tokens, copy with ~2.5k tokens
|
|
38
|
+
* of workspace context, extract/classify over ~1.5-3k tokens of scraped text;
|
|
39
|
+
* spend $1.80). Each model's mean cost on that set is taken as a multiple of
|
|
40
|
+
* a reference model whose REAL production mean is known from Langfuse, and
|
|
41
|
+
* that multiple is the projection. Harness: scripts/ops/ai-column-bakeoff
|
|
42
|
+
* (`--golden`, `summarize --reference`).
|
|
43
|
+
*
|
|
44
|
+
* tier model blind overall prod-mean $/row credits markup
|
|
45
|
+
* Fast gpt-6-luna 4.13 (res 4.80) 0.0013 (1.68x v4-flash @ $0.00077) 1.5 ~11x
|
|
46
|
+
* Balanced deepseek-v4.1-flash, no reasoning 4.23 0.0079 (0.60x v4-pro @ $0.0132) 4 ~5.1x
|
|
47
|
+
* Max gpt-6-sol 4.13 (5th; fastest, 1-3 s) 0.098 25 ~2.5x
|
|
48
|
+
*
|
|
49
|
+
* Balanced runs with reasoning OFF (`disableReasoning`): on the prod-shaped
|
|
50
|
+
* set it cost $0.00141/row against reasoning-on v4-pro's $0.00235 with
|
|
51
|
+
* classify, extract and research accuracy unchanged (100%; research citations
|
|
52
|
+
* 90%). With reasoning on it would not clear 5x at 4 credits.
|
|
53
|
+
*
|
|
54
|
+
* Max at ~2.5x is an EXPLICIT founder exception to the 5x rule (Philipp,
|
|
55
|
+
* 2026-09-26: "gpt-6 sol @ 2.5x markup"). Sol answered research correctly on
|
|
56
|
+
* every prod-shaped row and is the fastest of the 13. Two earlier Max picks
|
|
57
|
+
* were dropped the same day on cost: claude-opus-5.5 (#1, 4.63) billed ~1.8x
|
|
58
|
+
* the input tokens for the same rows, and kimi-k3 (#2, 4.37) projects to
|
|
59
|
+
* ~$0.183/row -- about 92 credits at 5x, its copy rows costing 48x v4-pro's.
|
|
60
|
+
* The first token-average projection (Langfuse token profile x list price)
|
|
61
|
+
* undercounted real cost by ~4x, which is why pricing now uses the
|
|
62
|
+
* calibration above. Neither never-shipped id is in the legacy map.
|
|
63
|
+
*
|
|
64
|
+
* Against Clay: fixed content models cost 0.2 Data Credits (GPT-5 Nano), 0.5
|
|
65
|
+
* (Gemini 2.5 Flash) and 1 (GPT-4.1/Helium) at >= $0.05 per Data Credit
|
|
66
|
+
* ($0.074-0.083 on plans) plus ~$0.01 per Action, so Fast and Balanced land
|
|
67
|
+
* well under Clay. Clay passes frontier models through at cost.
|
|
68
|
+
*
|
|
69
|
+
* Each tier falls back to a different vendor through OpenRouter's `models`
|
|
70
|
+
* routing, sent only when the tier default runs (ai-column-runner).
|
|
71
|
+
*
|
|
72
|
+
* The $49 trap, as an aside: one synthetic research row lists the real plan
|
|
73
|
+
* ($199/location/month) beside a $49 dental-school education price. 9 of 13
|
|
74
|
+
* models answered $49, including Luna, V4.1 Flash and Sol; Gemini 3.8 Flash
|
|
75
|
+
* (Max's fallback) was one of the four that did not.
|
|
76
|
+
*
|
|
77
|
+
* `reasoningEffort` is deliberately unset on every tier; the only reasoning
|
|
78
|
+
* control measured for columns is Balanced's off switch above.
|
|
79
|
+
* Retired ids live in LEGACY_MANAGED_AI_COLUMN_MODELS.
|
|
80
|
+
*/
|
|
24
81
|
ai_column: {
|
|
25
82
|
low: {
|
|
26
|
-
model: "
|
|
27
|
-
displayName: "
|
|
28
|
-
fallbackModels: [],
|
|
29
|
-
estPromptUsdPerM: 0.
|
|
30
|
-
estCompletionUsdPerM: 0.
|
|
83
|
+
model: "openai/gpt-6-luna",
|
|
84
|
+
displayName: "Oxygen Fast",
|
|
85
|
+
fallbackModels: ["deepseek/deepseek-v4.1-flash"],
|
|
86
|
+
estPromptUsdPerM: 0.1,
|
|
87
|
+
estCompletionUsdPerM: 0.5,
|
|
31
88
|
maxOutputTokens: 4096,
|
|
32
89
|
},
|
|
33
90
|
medium: {
|
|
34
|
-
model: "deepseek/deepseek-v4-
|
|
35
|
-
displayName: "
|
|
36
|
-
fallbackModels: [],
|
|
37
|
-
estPromptUsdPerM: 0.
|
|
38
|
-
estCompletionUsdPerM:
|
|
91
|
+
model: "deepseek/deepseek-v4.1-flash",
|
|
92
|
+
displayName: "Oxygen Balanced",
|
|
93
|
+
fallbackModels: ["openai/gpt-6-luna"],
|
|
94
|
+
estPromptUsdPerM: 0.3,
|
|
95
|
+
estCompletionUsdPerM: 1.2,
|
|
39
96
|
maxOutputTokens: 4096,
|
|
97
|
+
disableReasoning: true,
|
|
40
98
|
},
|
|
41
99
|
high: {
|
|
42
|
-
model: "
|
|
43
|
-
displayName: "
|
|
44
|
-
fallbackModels: [],
|
|
45
|
-
estPromptUsdPerM:
|
|
46
|
-
estCompletionUsdPerM:
|
|
100
|
+
model: "openai/gpt-6-sol",
|
|
101
|
+
displayName: "Oxygen Max",
|
|
102
|
+
fallbackModels: ["google/gemini-3.8-flash"],
|
|
103
|
+
estPromptUsdPerM: 2,
|
|
104
|
+
estCompletionUsdPerM: 10,
|
|
47
105
|
maxOutputTokens: 4096,
|
|
48
106
|
},
|
|
49
107
|
},
|
|
@@ -104,7 +162,7 @@ export const HOSTED_AI_MODEL_REGISTRY = {
|
|
|
104
162
|
// (classifyModelError sends it straight to the next model), so a 20%
|
|
105
163
|
// error rate here is a 20% double round trip on every delegation.
|
|
106
164
|
model: "deepseek/deepseek-v4-flash",
|
|
107
|
-
displayName: "
|
|
165
|
+
displayName: "Oxygen Fast",
|
|
108
166
|
// v4.1-flash earns its place HERE: it works 4 times in 5 and it is a
|
|
109
167
|
// genuinely different endpoint on a different provider, which is the one
|
|
110
168
|
// thing a fallback has to be.
|
|
@@ -116,7 +174,7 @@ export const HOSTED_AI_MODEL_REGISTRY = {
|
|
|
116
174
|
},
|
|
117
175
|
medium: {
|
|
118
176
|
model: "deepseek/deepseek-v4-pro",
|
|
119
|
-
displayName: "
|
|
177
|
+
displayName: "Oxygen Balanced",
|
|
120
178
|
fallbackModels: [],
|
|
121
179
|
reasoningEffort: "high",
|
|
122
180
|
estPromptUsdPerM: 0.435,
|
|
@@ -132,7 +190,7 @@ export const HOSTED_AI_MODEL_REGISTRY = {
|
|
|
132
190
|
*/
|
|
133
191
|
high: {
|
|
134
192
|
model: "moonshotai/kimi-k3",
|
|
135
|
-
displayName: "
|
|
193
|
+
displayName: "Oxygen Max",
|
|
136
194
|
fallbackModels: ["z-ai/glm-5.3"],
|
|
137
195
|
reasoningEffort: "medium",
|
|
138
196
|
estPromptUsdPerM: 1.7,
|
|
@@ -143,7 +201,7 @@ export const HOSTED_AI_MODEL_REGISTRY = {
|
|
|
143
201
|
agent: {
|
|
144
202
|
low: {
|
|
145
203
|
model: "deepseek/deepseek-v4-flash",
|
|
146
|
-
displayName: "
|
|
204
|
+
displayName: "Oxygen Fast",
|
|
147
205
|
fallbackModels: [],
|
|
148
206
|
reasoningEffort: "medium",
|
|
149
207
|
estPromptUsdPerM: 0.09,
|
|
@@ -152,7 +210,7 @@ export const HOSTED_AI_MODEL_REGISTRY = {
|
|
|
152
210
|
},
|
|
153
211
|
medium: {
|
|
154
212
|
model: "deepseek/deepseek-v4-pro",
|
|
155
|
-
displayName: "
|
|
213
|
+
displayName: "Oxygen Balanced",
|
|
156
214
|
fallbackModels: ["deepseek/deepseek-v4-flash"],
|
|
157
215
|
reasoningEffort: "high",
|
|
158
216
|
estPromptUsdPerM: 0.435,
|
|
@@ -161,7 +219,7 @@ export const HOSTED_AI_MODEL_REGISTRY = {
|
|
|
161
219
|
},
|
|
162
220
|
high: {
|
|
163
221
|
model: "moonshotai/kimi-k2.6",
|
|
164
|
-
displayName: "
|
|
222
|
+
displayName: "Oxygen Max",
|
|
165
223
|
fallbackModels: ["deepseek/deepseek-v4-pro"],
|
|
166
224
|
estPromptUsdPerM: 0.66,
|
|
167
225
|
estCompletionUsdPerM: 3.41,
|
|
@@ -169,6 +227,25 @@ export const HOSTED_AI_MODEL_REGISTRY = {
|
|
|
169
227
|
},
|
|
170
228
|
},
|
|
171
229
|
};
|
|
230
|
+
/**
|
|
231
|
+
* Managed AI-column model ids that USED to back a tier, mapped to the tier they
|
|
232
|
+
* billed at. A managed column saved with one of these pinned keeps validating
|
|
233
|
+
* (tenant-db `resolveManagedAiModelTier`) and runs on that tier's CURRENT model,
|
|
234
|
+
* so retiring a model never strands a saved column or keeps it on a model we no
|
|
235
|
+
* longer sell. A BYOK column keeps whatever it pinned: that is the customer's
|
|
236
|
+
* own key and choice. Retired 2026-09-26 (see the ai_column block above).
|
|
237
|
+
*/
|
|
238
|
+
export const LEGACY_MANAGED_AI_COLUMN_MODELS = {
|
|
239
|
+
"deepseek/deepseek-v4-flash": "low",
|
|
240
|
+
"deepseek/deepseek-v4-pro": "medium",
|
|
241
|
+
"moonshotai/kimi-k2.6": "high",
|
|
242
|
+
};
|
|
243
|
+
/** The tier a retired managed AI-column model id billed at, or null. */
|
|
244
|
+
export function legacyManagedAiColumnLevel(model) {
|
|
245
|
+
return Object.prototype.hasOwnProperty.call(LEGACY_MANAGED_AI_COLUMN_MODELS, model)
|
|
246
|
+
? LEGACY_MANAGED_AI_COLUMN_MODELS[model] ?? null
|
|
247
|
+
: null;
|
|
248
|
+
}
|
|
172
249
|
/**
|
|
173
250
|
* What a MANAGED AI column's reasoning tier is called in front of a customer.
|
|
174
251
|
*
|
|
@@ -258,8 +335,8 @@ function parseFallbackCsv(raw) {
|
|
|
258
335
|
* Resolve the model spec for a hosted-AI use case + reasoning level.
|
|
259
336
|
*
|
|
260
337
|
* For `copilot`, Doppler overrides are applied here:
|
|
261
|
-
* - `OXYGEN_COPILOT_MODEL_{LOW,MEDIUM,HIGH}` replaces `.model`
|
|
262
|
-
*
|
|
338
|
+
* - `OXYGEN_COPILOT_MODEL_{LOW,MEDIUM,HIGH}` replaces `.model`; `.displayName`
|
|
339
|
+
* keeps the tier label, because a customer never sees a model id.
|
|
263
340
|
* - `OXYGEN_COPILOT_FALLBACK_{LOW,MEDIUM,HIGH}` replaces `.fallbackModels`
|
|
264
341
|
* from a comma-separated list.
|
|
265
342
|
*
|
|
@@ -311,13 +388,158 @@ export function resolveHostedAiModel(input) {
|
|
|
311
388
|
const resolved = { ...base };
|
|
312
389
|
if (modelOverride !== undefined) {
|
|
313
390
|
resolved.model = modelOverride;
|
|
314
|
-
resolved.displayName = modelOverride;
|
|
315
391
|
}
|
|
316
392
|
if (fallbackOverride !== undefined) {
|
|
317
393
|
resolved.fallbackModels = fallbackOverride;
|
|
318
394
|
}
|
|
319
395
|
return resolved;
|
|
320
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* "DO not expose MODELs names to users" (Philipp, 2026-09-26).
|
|
399
|
+
*
|
|
400
|
+
* A customer buys an Oxygen tier, so every customer surface names the tier
|
|
401
|
+
* (Oxygen Fast / Balanced / Max / Auto) and never the vendor model OXYGEN picked
|
|
402
|
+
* behind it. The model ids stay in logs, Langfuse and staff surfaces. The only
|
|
403
|
+
* model a customer sees is one they chose themselves: a BYOK pin or a Copilot
|
|
404
|
+
* `--model` request, which callers pass as `allow`.
|
|
405
|
+
*
|
|
406
|
+
* Provenance written before this rule (cell sources, run events, Copilot events)
|
|
407
|
+
* still carries raw ids, so the redaction runs where a payload leaves for a
|
|
408
|
+
* customer rather than where it is written.
|
|
409
|
+
*/
|
|
410
|
+
const BACKUP_MODEL_LABEL = "Oxygen backup model";
|
|
411
|
+
// TypeSafe's System One classifier (`SYSTEM_ONE_MODEL` in @oxygen/integrations,
|
|
412
|
+
// pinned as `jev-<version>`) is OXYGEN's choice too. It answers classification
|
|
413
|
+
// columns and inbox triage at the managed Fast price, so provenance written with
|
|
414
|
+
// its id (before the write-time fix, or by a later version bump) reads as that
|
|
415
|
+
// tier. Matched by shape so a version bump stays covered.
|
|
416
|
+
const SYSTEM_ONE_MODEL_ID = /^jev-(?:\d+(?:\.\d+)*|latest)$/;
|
|
417
|
+
const SYSTEM_ONE_MODEL_LABEL = MANAGED_AI_TIER_LABELS.low;
|
|
418
|
+
// Doppler can swap any tier's model without a deploy; the swapped-in id is still
|
|
419
|
+
// OXYGEN's choice, so it is redacted like the registry default it replaces.
|
|
420
|
+
const MODEL_OVERRIDE_ENV_PREFIXES = ["OXYGEN_OPENROUTER_MODEL_", "OXYGEN_COPILOT_MODEL_", "OXYGEN_AGENT_MODEL_"];
|
|
421
|
+
function managedModelLabels(preferredUseCase, env = typeof process === "undefined" ? {} : process.env) {
|
|
422
|
+
const labels = new Map();
|
|
423
|
+
for (const prefix of MODEL_OVERRIDE_ENV_PREFIXES) {
|
|
424
|
+
for (const level of ["LOW", "MEDIUM", "HIGH", "AUTO"]) {
|
|
425
|
+
const model = readTrimmedEnv(env, `${prefix}${level}`);
|
|
426
|
+
if (!model || labels.has(model))
|
|
427
|
+
continue;
|
|
428
|
+
labels.set(model, level === "AUTO" ? COPILOT_TIER_LABELS.auto : MANAGED_AI_TIER_LABELS[level.toLowerCase()]);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const useCases = Object.keys(HOSTED_AI_MODEL_REGISTRY);
|
|
432
|
+
const ordered = preferredUseCase
|
|
433
|
+
? [preferredUseCase, ...useCases.filter((useCase) => useCase !== preferredUseCase)]
|
|
434
|
+
: useCases;
|
|
435
|
+
for (const useCase of ordered) {
|
|
436
|
+
for (const spec of Object.values(HOSTED_AI_MODEL_REGISTRY[useCase])) {
|
|
437
|
+
if (!labels.has(spec.model))
|
|
438
|
+
labels.set(spec.model, spec.displayName);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
for (const [model, level] of Object.entries(LEGACY_MANAGED_AI_COLUMN_MODELS)) {
|
|
442
|
+
if (!labels.has(model))
|
|
443
|
+
labels.set(model, MANAGED_AI_TIER_LABELS[level]);
|
|
444
|
+
}
|
|
445
|
+
for (const useCase of ordered) {
|
|
446
|
+
for (const spec of Object.values(HOSTED_AI_MODEL_REGISTRY[useCase])) {
|
|
447
|
+
for (const fallback of spec.fallbackModels) {
|
|
448
|
+
if (!labels.has(fallback))
|
|
449
|
+
labels.set(fallback, BACKUP_MODEL_LABEL);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return labels;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* The customer-facing label for a model id OXYGEN manages, or null when the id is
|
|
457
|
+
* not one of ours (a BYOK pin, a customer's own request). OpenRouter can answer
|
|
458
|
+
* with a dated or routed variant (`vendor/model-20260915`, `vendor/model:nitro`),
|
|
459
|
+
* so those match their base id.
|
|
460
|
+
*/
|
|
461
|
+
export function managedModelLabel(model, preferredUseCase) {
|
|
462
|
+
return labelForManagedModel(managedModelLabels(preferredUseCase), model);
|
|
463
|
+
}
|
|
464
|
+
function labelForManagedModel(labels, model) {
|
|
465
|
+
const exact = labels.get(model);
|
|
466
|
+
if (exact)
|
|
467
|
+
return exact;
|
|
468
|
+
for (const [id, label] of labels) {
|
|
469
|
+
if (model.startsWith(`${id}-`) || model.startsWith(`${id}:`))
|
|
470
|
+
return label;
|
|
471
|
+
}
|
|
472
|
+
if (SYSTEM_ONE_MODEL_ID.test(model))
|
|
473
|
+
return SYSTEM_ONE_MODEL_LABEL;
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* What an AI-column run's provenance records as its model: the tier label for
|
|
478
|
+
* a managed run, and the model itself only when the customer pinned it on
|
|
479
|
+
* their own key. A BYOK run on the tier default is still OXYGEN's pick.
|
|
480
|
+
*/
|
|
481
|
+
export function customerFacingAiColumnModel(input) {
|
|
482
|
+
if (input.credentialMode === "byok" && managedModelLabel(input.model, "ai_column") === null)
|
|
483
|
+
return input.model;
|
|
484
|
+
return MANAGED_AI_TIER_LABELS[input.reasoningLevel];
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* The model OpenRouter reports it answered with, for the same provenance. A
|
|
488
|
+
* managed run may have been served by a fallback, so it reads as the tier; a
|
|
489
|
+
* customer's own pin (or its dated variant) stays theirs.
|
|
490
|
+
*/
|
|
491
|
+
export function customerFacingAiColumnResponseModel(input) {
|
|
492
|
+
if (input.responseModel === null)
|
|
493
|
+
return null;
|
|
494
|
+
const requested = customerFacingAiColumnModel(input);
|
|
495
|
+
return requested === input.model ? input.responseModel : requested;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* An AI-column request summary as the customer reads it: its `model` is the
|
|
499
|
+
* tier (or their pin), and any other managed id inside is redacted too.
|
|
500
|
+
*/
|
|
501
|
+
export function customerFacingAiColumnRequest(summary, input) {
|
|
502
|
+
const redacted = redactManagedModelIds(summary, { preferredUseCase: "ai_column" });
|
|
503
|
+
return "model" in summary ? { ...redacted, model: customerFacingAiColumnModel(input) } : redacted;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Replace every string in `value` that is a managed model id with its tier
|
|
507
|
+
* label. Walks objects and arrays; only a string that IS a model id is touched,
|
|
508
|
+
* so prose that merely mentions a model is left alone. `allow` lists ids the
|
|
509
|
+
* customer chose (a BYOK pin), which pass through untouched.
|
|
510
|
+
*/
|
|
511
|
+
export function redactManagedModelIds(value, options = {}) {
|
|
512
|
+
const allow = new Set((options.allow ?? []).filter((id) => typeof id === "string" && id.length > 0));
|
|
513
|
+
const labels = managedModelLabels(options.preferredUseCase, options.env);
|
|
514
|
+
const labelFor = (model) => allow.has(model) ? null : labelForManagedModel(labels, model);
|
|
515
|
+
const walk = (node) => {
|
|
516
|
+
if (typeof node === "string")
|
|
517
|
+
return labelFor(node) ?? node;
|
|
518
|
+
if (Array.isArray(node)) {
|
|
519
|
+
let changed = false;
|
|
520
|
+
const next = node.map((item) => {
|
|
521
|
+
const mapped = walk(item);
|
|
522
|
+
if (mapped !== item)
|
|
523
|
+
changed = true;
|
|
524
|
+
return mapped;
|
|
525
|
+
});
|
|
526
|
+
return changed ? next : node;
|
|
527
|
+
}
|
|
528
|
+
if (node && typeof node === "object" && !(node instanceof Date)) {
|
|
529
|
+
let changed = false;
|
|
530
|
+
const next = {};
|
|
531
|
+
for (const [key, item] of Object.entries(node)) {
|
|
532
|
+
const mapped = walk(item);
|
|
533
|
+
if (mapped !== item)
|
|
534
|
+
changed = true;
|
|
535
|
+
next[key] = mapped;
|
|
536
|
+
}
|
|
537
|
+
return changed ? next : node;
|
|
538
|
+
}
|
|
539
|
+
return node;
|
|
540
|
+
};
|
|
541
|
+
return walk(value);
|
|
542
|
+
}
|
|
321
543
|
/**
|
|
322
544
|
* Convenience map of each reasoning tier's default `.model` id for a use case.
|
|
323
545
|
* `@oxygen/integrations` and `@oxygen/tenant-db` re-source their AI-column
|
|
@@ -13,9 +13,27 @@
|
|
|
13
13
|
/**
|
|
14
14
|
* Formats that must be held in memory to parse at all — a JSON array has no
|
|
15
15
|
* record boundary to stream from, and XLSX is a zip container. CSV and JSONL
|
|
16
|
-
* stream, so only these two
|
|
16
|
+
* stream from object storage up to the plan's file limit, so only these two
|
|
17
|
+
* carry this lower ceiling (raised from 10 MiB by decision F.8, 2026-09-27).
|
|
18
|
+
*
|
|
19
|
+
* It also bounds every path that holds a whole file in one request: the inline
|
|
20
|
+
* multipart import used when object storage is not configured.
|
|
17
21
|
*/
|
|
18
22
|
export declare const MAX_BUFFERED_IMPORT_PARSE_BYTES: number;
|
|
23
|
+
/**
|
|
24
|
+
* A link import (`--url`, `oxygen_tables_import_url`, the wizard's link card)
|
|
25
|
+
* is downloaded and parsed inside the web request, so it keeps its own small
|
|
26
|
+
* ceiling. It is not a plan limit: a bigger file is downloaded and imported
|
|
27
|
+
* with `tables import --file`, which stages it in object storage.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_URL_IMPORT_BYTES: number;
|
|
30
|
+
/**
|
|
31
|
+
* The largest object one presigned PUT may carry on an S3-compatible store (S3
|
|
32
|
+
* and Ceph-based stores such as Hetzner Object Storage cap a single PUT at
|
|
33
|
+
* 5 GiB). A staged import is one PUT, so no plan's import file limit may
|
|
34
|
+
* exceed it.
|
|
35
|
+
*/
|
|
36
|
+
export declare const IMPORT_OBJECT_SINGLE_PUT_MAX_BYTES: number;
|
|
19
37
|
/**
|
|
20
38
|
* The platform's request-body ceiling — infrastructure, not a plan limit.
|
|
21
39
|
*
|
|
@@ -26,3 +44,9 @@ export declare const MAX_BUFFERED_IMPORT_PARSE_BYTES: number;
|
|
|
26
44
|
export declare const VERCEL_REQUEST_BODY_LIMIT_BYTES: number;
|
|
27
45
|
/** True for the formats that cannot stream and so carry the buffered ceiling. */
|
|
28
46
|
export declare function isBufferedOnlyImportFormat(format: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The refusal every surface gives a JSON or XLSX file over the buffered
|
|
49
|
+
* ceiling. It names the ceiling and the two formats that take a larger file,
|
|
50
|
+
* so the reader knows the fix is a format change, not a plan change.
|
|
51
|
+
*/
|
|
52
|
+
export declare function bufferedImportTooLargeMessage(format: string, fileBytes?: number | null): string;
|