@aexhq/sdk 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_contracts/models.d.ts +32 -0
- package/dist/_contracts/models.js +28 -2
- package/dist/_contracts/operations.d.ts +17 -1
- package/dist/_contracts/operations.js +50 -0
- package/dist/_contracts/provider-support.d.ts +68 -0
- package/dist/_contracts/provider-support.js +28 -0
- package/dist/_contracts/run-cost.d.ts +27 -0
- package/dist/_contracts/run-cost.js +34 -1
- package/dist/_contracts/run-custody.js +94 -3
- package/dist/_contracts/runtime-types.d.ts +32 -0
- package/dist/_contracts/submission.d.ts +57 -1
- package/dist/_contracts/submission.js +103 -4
- package/dist/cli.mjs +165 -5
- package/dist/cli.mjs.sha256 +1 -1
- package/dist/client.d.ts +79 -1
- package/dist/client.js +80 -2
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/secret.d.ts +102 -0
- package/dist/secret.js +148 -0
- package/dist/secret.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/provider-runtime-capabilities.md +23 -1
- package/package.json +2 -2
|
@@ -62,6 +62,14 @@ export declare const MODEL_PROVIDER_IDS: {
|
|
|
62
62
|
readonly "mistral-small-latest": {
|
|
63
63
|
readonly mistral: "mistral-small-latest";
|
|
64
64
|
};
|
|
65
|
+
readonly "doubao-seed-pro": {
|
|
66
|
+
readonly doubao: "doubao-seed-1-8-251228";
|
|
67
|
+
readonly "doubao-cn": "doubao-seed-1-8-251228";
|
|
68
|
+
};
|
|
69
|
+
readonly "doubao-seed-flash": {
|
|
70
|
+
readonly doubao: "doubao-seed-1-6-flash-250828";
|
|
71
|
+
readonly "doubao-cn": "doubao-seed-1-6-flash-250828";
|
|
72
|
+
};
|
|
65
73
|
};
|
|
66
74
|
/**
|
|
67
75
|
* Closed set of canonical model ids accepted by the public run-submission
|
|
@@ -116,6 +124,18 @@ export declare const Models: {
|
|
|
116
124
|
readonly MISTRAL_LARGE_LATEST: "mistral-large-latest";
|
|
117
125
|
/** Mistral Small (latest) — Mistral. */
|
|
118
126
|
readonly MISTRAL_SMALL_LATEST: "mistral-small-latest";
|
|
127
|
+
/**
|
|
128
|
+
* Doubao Seed 1.8 — ByteDance, via the official Ark API. Default routes
|
|
129
|
+
* through the international BytePlus gateway (`Providers.DOUBAO`); pair with
|
|
130
|
+
* `Providers.DOUBAO_CN` for the China Volcengine gateway.
|
|
131
|
+
*/
|
|
132
|
+
readonly DOUBAO_SEED_PRO: "doubao-seed-pro";
|
|
133
|
+
/**
|
|
134
|
+
* Doubao Seed 1.6 Flash — ByteDance, via the official Ark API (fast/cheap).
|
|
135
|
+
* Default `Providers.DOUBAO` (international); pair with `Providers.DOUBAO_CN`
|
|
136
|
+
* for China.
|
|
137
|
+
*/
|
|
138
|
+
readonly DOUBAO_SEED_FLASH: "doubao-seed-flash";
|
|
119
139
|
};
|
|
120
140
|
/**
|
|
121
141
|
* Back-compat alias for {@link Models}. Existing imports of `RunModels`
|
|
@@ -159,6 +179,18 @@ export declare const RunModels: {
|
|
|
159
179
|
readonly MISTRAL_LARGE_LATEST: "mistral-large-latest";
|
|
160
180
|
/** Mistral Small (latest) — Mistral. */
|
|
161
181
|
readonly MISTRAL_SMALL_LATEST: "mistral-small-latest";
|
|
182
|
+
/**
|
|
183
|
+
* Doubao Seed 1.8 — ByteDance, via the official Ark API. Default routes
|
|
184
|
+
* through the international BytePlus gateway (`Providers.DOUBAO`); pair with
|
|
185
|
+
* `Providers.DOUBAO_CN` for the China Volcengine gateway.
|
|
186
|
+
*/
|
|
187
|
+
readonly DOUBAO_SEED_PRO: "doubao-seed-pro";
|
|
188
|
+
/**
|
|
189
|
+
* Doubao Seed 1.6 Flash — ByteDance, via the official Ark API (fast/cheap).
|
|
190
|
+
* Default `Providers.DOUBAO` (international); pair with `Providers.DOUBAO_CN`
|
|
191
|
+
* for China.
|
|
192
|
+
*/
|
|
193
|
+
readonly DOUBAO_SEED_FLASH: "doubao-seed-flash";
|
|
162
194
|
};
|
|
163
195
|
/**
|
|
164
196
|
* Provider → canonical models that provider can serve. Derived from
|
|
@@ -30,7 +30,21 @@ export const MODEL_PROVIDER_IDS = {
|
|
|
30
30
|
"gemini-2.0-flash": { gemini: "gemini-2.0-flash", openrouter: "google/gemini-2.0-flash-001" },
|
|
31
31
|
"gemini-2.5-flash": { gemini: "gemini-2.5-flash" },
|
|
32
32
|
"mistral-large-latest": { mistral: "mistral-large-latest" },
|
|
33
|
-
"mistral-small-latest": { mistral: "mistral-small-latest" }
|
|
33
|
+
"mistral-small-latest": { mistral: "mistral-small-latest" },
|
|
34
|
+
// Doubao (ByteDance) via the official Ark API. Served by both the
|
|
35
|
+
// international BytePlus ModelArk gateway (`doubao`, the default) and the
|
|
36
|
+
// China Volcengine Ark gateway (`doubao-cn`). Ark accepts the API-format
|
|
37
|
+
// model NAME directly in the chat-completions `model` field (no `ep-…`
|
|
38
|
+
// inference-endpoint id). The native strings are the same Ark catalog ids on
|
|
39
|
+
// both gateways; BytePlus per-account availability is confirmed at live-verify
|
|
40
|
+
// (both providers ship `live-unverified` until then — provider-support.ts).
|
|
41
|
+
// pro — Doubao Seed 1.8 (flagship, 256K context).
|
|
42
|
+
// flash — Doubao Seed 1.6 Flash (fast/cheap, 256K context).
|
|
43
|
+
"doubao-seed-pro": { doubao: "doubao-seed-1-8-251228", "doubao-cn": "doubao-seed-1-8-251228" },
|
|
44
|
+
"doubao-seed-flash": {
|
|
45
|
+
doubao: "doubao-seed-1-6-flash-250828",
|
|
46
|
+
"doubao-cn": "doubao-seed-1-6-flash-250828"
|
|
47
|
+
}
|
|
34
48
|
};
|
|
35
49
|
export const RUN_MODELS = Object.keys(MODEL_PROVIDER_IDS);
|
|
36
50
|
/**
|
|
@@ -79,7 +93,19 @@ export const Models = {
|
|
|
79
93
|
/** Mistral Large (latest) — Mistral. */
|
|
80
94
|
MISTRAL_LARGE_LATEST: "mistral-large-latest",
|
|
81
95
|
/** Mistral Small (latest) — Mistral. */
|
|
82
|
-
MISTRAL_SMALL_LATEST: "mistral-small-latest"
|
|
96
|
+
MISTRAL_SMALL_LATEST: "mistral-small-latest",
|
|
97
|
+
/**
|
|
98
|
+
* Doubao Seed 1.8 — ByteDance, via the official Ark API. Default routes
|
|
99
|
+
* through the international BytePlus gateway (`Providers.DOUBAO`); pair with
|
|
100
|
+
* `Providers.DOUBAO_CN` for the China Volcengine gateway.
|
|
101
|
+
*/
|
|
102
|
+
DOUBAO_SEED_PRO: "doubao-seed-pro",
|
|
103
|
+
/**
|
|
104
|
+
* Doubao Seed 1.6 Flash — ByteDance, via the official Ark API (fast/cheap).
|
|
105
|
+
* Default `Providers.DOUBAO` (international); pair with `Providers.DOUBAO_CN`
|
|
106
|
+
* for China.
|
|
107
|
+
*/
|
|
108
|
+
DOUBAO_SEED_FLASH: "doubao-seed-flash"
|
|
83
109
|
};
|
|
84
110
|
/**
|
|
85
111
|
* Back-compat alias for {@link Models}. Existing imports of `RunModels`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HttpClient } from "./http.js";
|
|
2
2
|
import type { RunUnit } from "./run-unit.js";
|
|
3
|
-
import type { AgentsMdRecord, FileRecord, Output, OutputFileDownload, OutputFileSelector, Run, RunEvent, SignedOutputLink, Skill, WhoAmI } from "./runtime-types.js";
|
|
3
|
+
import type { AgentsMdRecord, FileRecord, Output, OutputFileDownload, OutputFileSelector, Run, RunEvent, SecretRecord, SecretReveal, SignedOutputLink, Skill, WhoAmI } from "./runtime-types.js";
|
|
4
4
|
import type { PlatformRunSubmissionInput } from "./submission.js";
|
|
5
5
|
/**
|
|
6
6
|
* The single source of truth for SDK<->BFF transport. The SDK class
|
|
@@ -207,6 +207,22 @@ export declare function createFile(http: HttpClient, args: {
|
|
|
207
207
|
export declare function listFiles(http: HttpClient): Promise<readonly FileRecord[]>;
|
|
208
208
|
export declare function getFile(http: HttpClient, fileId: string): Promise<FileRecord>;
|
|
209
209
|
export declare function deleteFile(http: HttpClient, fileId: string): Promise<void>;
|
|
210
|
+
/** Create a named workspace secret. The value travels in the body. */
|
|
211
|
+
export declare function createSecret(http: HttpClient, args: {
|
|
212
|
+
readonly name: string;
|
|
213
|
+
readonly value: string;
|
|
214
|
+
}): Promise<SecretRecord>;
|
|
215
|
+
export declare function listSecrets(http: HttpClient): Promise<readonly SecretRecord[]>;
|
|
216
|
+
/** Metadata for one workspace secret by name. Never returns the value. */
|
|
217
|
+
export declare function getSecret(http: HttpClient, name: string): Promise<SecretRecord>;
|
|
218
|
+
/** Audited value read — the only path that returns a workspace secret value. */
|
|
219
|
+
export declare function revealSecret(http: HttpClient, name: string): Promise<SecretReveal>;
|
|
220
|
+
/** Replace the value of an existing workspace secret; bumps its version. */
|
|
221
|
+
export declare function rotateSecret(http: HttpClient, args: {
|
|
222
|
+
readonly name: string;
|
|
223
|
+
readonly value: string;
|
|
224
|
+
}): Promise<SecretRecord>;
|
|
225
|
+
export declare function deleteSecret(http: HttpClient, name: string): Promise<void>;
|
|
210
226
|
export interface AssetUploadResult {
|
|
211
227
|
readonly assetId: string;
|
|
212
228
|
readonly contentHash: string;
|
|
@@ -541,6 +541,56 @@ function unwrapFile(result) {
|
|
|
541
541
|
}
|
|
542
542
|
return result;
|
|
543
543
|
}
|
|
544
|
+
// ===========================================================================
|
|
545
|
+
// Workspace secret operations
|
|
546
|
+
//
|
|
547
|
+
// Value-bearing requests (create/rotate) carry the value in the JSON BODY,
|
|
548
|
+
// never the URL/query, so it never lands in logs or the request line. Reads
|
|
549
|
+
// split by sensitivity: `getSecret`/`listSecrets` return METADATA only;
|
|
550
|
+
// `revealSecret` is the audited value read (POST so it's a logged action).
|
|
551
|
+
// ===========================================================================
|
|
552
|
+
/** Create a named workspace secret. The value travels in the body. */
|
|
553
|
+
export async function createSecret(http, args) {
|
|
554
|
+
const result = await http.request("/api/secrets", {
|
|
555
|
+
method: "POST",
|
|
556
|
+
body: JSON.stringify({ name: args.name, value: args.value })
|
|
557
|
+
});
|
|
558
|
+
return unwrapSecret(result);
|
|
559
|
+
}
|
|
560
|
+
export async function listSecrets(http) {
|
|
561
|
+
const result = await http.request("/api/secrets");
|
|
562
|
+
if (Array.isArray(result)) {
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
return result.secrets;
|
|
566
|
+
}
|
|
567
|
+
/** Metadata for one workspace secret by name. Never returns the value. */
|
|
568
|
+
export async function getSecret(http, name) {
|
|
569
|
+
const result = await http.request(`/api/secrets/${encodeURIComponent(name)}`);
|
|
570
|
+
return unwrapSecret(result);
|
|
571
|
+
}
|
|
572
|
+
/** Audited value read — the only path that returns a workspace secret value. */
|
|
573
|
+
export async function revealSecret(http, name) {
|
|
574
|
+
return http.request(`/api/secrets/${encodeURIComponent(name)}/reveal`, {
|
|
575
|
+
method: "POST"
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/** Replace the value of an existing workspace secret; bumps its version. */
|
|
579
|
+
export async function rotateSecret(http, args) {
|
|
580
|
+
const result = await http.request(`/api/secrets/${encodeURIComponent(args.name)}/rotate`, { method: "POST", body: JSON.stringify({ value: args.value }) });
|
|
581
|
+
return unwrapSecret(result);
|
|
582
|
+
}
|
|
583
|
+
export async function deleteSecret(http, name) {
|
|
584
|
+
await http.request(`/api/secrets/${encodeURIComponent(name)}`, {
|
|
585
|
+
method: "DELETE"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
function unwrapSecret(result) {
|
|
589
|
+
if (result && typeof result === "object" && "secret" in result) {
|
|
590
|
+
return result.secret;
|
|
591
|
+
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
544
594
|
function unwrapSkill(result) {
|
|
545
595
|
if (result && typeof result === "object" && "skill" in result) {
|
|
546
596
|
return result.skill;
|
|
@@ -256,5 +256,73 @@ export declare const PROVIDER_PUBLIC_SUPPORT: {
|
|
|
256
256
|
}];
|
|
257
257
|
};
|
|
258
258
|
};
|
|
259
|
+
readonly doubao: {
|
|
260
|
+
readonly displayName: "Doubao";
|
|
261
|
+
readonly status: "live-unverified";
|
|
262
|
+
readonly docsAnchor: "doubao";
|
|
263
|
+
readonly docs: readonly [{
|
|
264
|
+
readonly label: "Credentials";
|
|
265
|
+
readonly href: "credentials.md";
|
|
266
|
+
}, {
|
|
267
|
+
readonly label: "Events";
|
|
268
|
+
readonly href: "events.md";
|
|
269
|
+
}];
|
|
270
|
+
readonly evidence: readonly [{
|
|
271
|
+
readonly label: "Submission parser and routing parity";
|
|
272
|
+
readonly href: "../../contracts/test/submission.test.ts";
|
|
273
|
+
}, {
|
|
274
|
+
readonly label: "Runtime support validator";
|
|
275
|
+
readonly href: "../../contracts/test/runtime-support.test.ts";
|
|
276
|
+
}, {
|
|
277
|
+
readonly label: "Generated matrix freshness";
|
|
278
|
+
readonly href: "../../../scripts/validate/capability-matrix.test.ts";
|
|
279
|
+
}];
|
|
280
|
+
readonly runtimeEvidence: {
|
|
281
|
+
readonly managed: readonly [{
|
|
282
|
+
readonly label: "Submission parser and routing parity";
|
|
283
|
+
readonly href: "../../contracts/test/submission.test.ts";
|
|
284
|
+
}, {
|
|
285
|
+
readonly label: "Runtime support validator";
|
|
286
|
+
readonly href: "../../contracts/test/runtime-support.test.ts";
|
|
287
|
+
}, {
|
|
288
|
+
readonly label: "Generated matrix freshness";
|
|
289
|
+
readonly href: "../../../scripts/validate/capability-matrix.test.ts";
|
|
290
|
+
}];
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
readonly "doubao-cn": {
|
|
294
|
+
readonly displayName: "Doubao (China)";
|
|
295
|
+
readonly status: "live-unverified";
|
|
296
|
+
readonly docsAnchor: "doubao-cn";
|
|
297
|
+
readonly docs: readonly [{
|
|
298
|
+
readonly label: "Credentials";
|
|
299
|
+
readonly href: "credentials.md";
|
|
300
|
+
}, {
|
|
301
|
+
readonly label: "Events";
|
|
302
|
+
readonly href: "events.md";
|
|
303
|
+
}];
|
|
304
|
+
readonly evidence: readonly [{
|
|
305
|
+
readonly label: "Submission parser and routing parity";
|
|
306
|
+
readonly href: "../../contracts/test/submission.test.ts";
|
|
307
|
+
}, {
|
|
308
|
+
readonly label: "Runtime support validator";
|
|
309
|
+
readonly href: "../../contracts/test/runtime-support.test.ts";
|
|
310
|
+
}, {
|
|
311
|
+
readonly label: "Generated matrix freshness";
|
|
312
|
+
readonly href: "../../../scripts/validate/capability-matrix.test.ts";
|
|
313
|
+
}];
|
|
314
|
+
readonly runtimeEvidence: {
|
|
315
|
+
readonly managed: readonly [{
|
|
316
|
+
readonly label: "Submission parser and routing parity";
|
|
317
|
+
readonly href: "../../contracts/test/submission.test.ts";
|
|
318
|
+
}, {
|
|
319
|
+
readonly label: "Runtime support validator";
|
|
320
|
+
readonly href: "../../contracts/test/runtime-support.test.ts";
|
|
321
|
+
}, {
|
|
322
|
+
readonly label: "Generated matrix freshness";
|
|
323
|
+
readonly href: "../../../scripts/validate/capability-matrix.test.ts";
|
|
324
|
+
}];
|
|
325
|
+
};
|
|
326
|
+
};
|
|
259
327
|
};
|
|
260
328
|
export declare function providerPublicSupport(provider: RunProvider): ProviderPublicSupport;
|
|
@@ -109,6 +109,34 @@ export const PROVIDER_PUBLIC_SUPPORT = {
|
|
|
109
109
|
runtimeEvidence: {
|
|
110
110
|
managed: COMMON_EVIDENCE
|
|
111
111
|
}
|
|
112
|
+
},
|
|
113
|
+
// Doubao (ByteDance) via the official Ark API — international BytePlus gateway.
|
|
114
|
+
// Wired + parser/routing-verified but not yet proven against a live BytePlus
|
|
115
|
+
// account, so `live-unverified`. Promote to `supported` (and swap COMMON_EVIDENCE
|
|
116
|
+
// for a DOUBAO_MANAGED_EVIDENCE pointer to live-sdk-doubao.test.ts) once the
|
|
117
|
+
// live run passes.
|
|
118
|
+
doubao: {
|
|
119
|
+
displayName: "Doubao",
|
|
120
|
+
status: "live-unverified",
|
|
121
|
+
docsAnchor: "doubao",
|
|
122
|
+
docs: COMMON_DOCS,
|
|
123
|
+
evidence: COMMON_EVIDENCE,
|
|
124
|
+
runtimeEvidence: {
|
|
125
|
+
managed: COMMON_EVIDENCE
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
// Doubao (ByteDance) via the official Ark API — China Volcengine gateway.
|
|
129
|
+
// Same wiring as `doubao`; additionally gated on CF Worker egress reaching the
|
|
130
|
+
// Beijing host (see apps/egress-probe). `live-unverified` until proven live.
|
|
131
|
+
"doubao-cn": {
|
|
132
|
+
displayName: "Doubao (China)",
|
|
133
|
+
status: "live-unverified",
|
|
134
|
+
docsAnchor: "doubao-cn",
|
|
135
|
+
docs: COMMON_DOCS,
|
|
136
|
+
evidence: COMMON_EVIDENCE,
|
|
137
|
+
runtimeEvidence: {
|
|
138
|
+
managed: COMMON_EVIDENCE
|
|
139
|
+
}
|
|
112
140
|
}
|
|
113
141
|
};
|
|
114
142
|
export function providerPublicSupport(provider) {
|
|
@@ -92,6 +92,21 @@ export interface RunCostManagedKeyBudgetTelemetry {
|
|
|
92
92
|
readonly chargedCreditUnits?: number;
|
|
93
93
|
readonly releasedCreditUnits?: number;
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The basis for a {@link RunCostTelemetry.billedCostUsd}: an honest marker of
|
|
97
|
+
* whether the figure is a settle-time ESTIMATE or has been RECONCILED against
|
|
98
|
+
* authoritative actuals. Deliberately carries NO rate-card version or unit
|
|
99
|
+
* rates — the platform's public-safe convention treats `rateCard`/`margin` as
|
|
100
|
+
* private tokens (run-cost.test.ts privateCostFieldPattern), so the version the
|
|
101
|
+
* figure was derived under stays internal (recorded only in the platform's
|
|
102
|
+
* internal raw-usage export).
|
|
103
|
+
*/
|
|
104
|
+
export declare const RUN_COST_BASIS_STATUSES: readonly ["estimated", "reconciled"];
|
|
105
|
+
export type RunCostBasisStatus = (typeof RUN_COST_BASIS_STATUSES)[number];
|
|
106
|
+
export interface RunCostBasis {
|
|
107
|
+
readonly currency: "USD";
|
|
108
|
+
readonly status: RunCostBasisStatus;
|
|
109
|
+
}
|
|
95
110
|
export interface RunCostTelemetry {
|
|
96
111
|
readonly schemaVersion: typeof RUN_COST_TELEMETRY_SCHEMA_VERSION;
|
|
97
112
|
readonly runId?: string;
|
|
@@ -108,6 +123,18 @@ export interface RunCostTelemetry {
|
|
|
108
123
|
readonly storage?: RunCostStorageTelemetry;
|
|
109
124
|
readonly proxy?: RunCostProxyTelemetry;
|
|
110
125
|
readonly managedKey?: RunCostManagedKeyBudgetTelemetry;
|
|
126
|
+
/**
|
|
127
|
+
* Customer-facing AEX cost of serving this run, USD — a REPORTED ESTIMATE,
|
|
128
|
+
* not a charge (telemetry/showback only; no invoicing or credit deduction).
|
|
129
|
+
* = rawCostUsd × marginMultiplier (margin currently a global 1.0). EXCLUDES
|
|
130
|
+
* the customer's BYOK provider spend. The raw (pre-margin) figure is kept
|
|
131
|
+
* internal and never appears on this public-safe shape. A plain number, so it
|
|
132
|
+
* passes the run-record public-safe archive scan. Absent when the run incurred
|
|
133
|
+
* no priced AEX usage.
|
|
134
|
+
*/
|
|
135
|
+
readonly billedCostUsd?: number;
|
|
136
|
+
/** Currency + estimate/reconciled basis for {@link billedCostUsd}. */
|
|
137
|
+
readonly costBasis?: RunCostBasis;
|
|
111
138
|
}
|
|
112
139
|
export type RunCostTelemetryInput = Omit<RunCostTelemetry, "schemaVersion">;
|
|
113
140
|
export interface RunCostTelemetryFromUsageSamplesInput {
|
|
@@ -96,6 +96,16 @@ const RUN_USAGE_SAMPLE_METRIC_UNITS = {
|
|
|
96
96
|
"managed_key.charged_credit_units": "credit_unit",
|
|
97
97
|
"managed_key.released_credit_units": "credit_unit"
|
|
98
98
|
};
|
|
99
|
+
/**
|
|
100
|
+
* The basis for a {@link RunCostTelemetry.billedCostUsd}: an honest marker of
|
|
101
|
+
* whether the figure is a settle-time ESTIMATE or has been RECONCILED against
|
|
102
|
+
* authoritative actuals. Deliberately carries NO rate-card version or unit
|
|
103
|
+
* rates — the platform's public-safe convention treats `rateCard`/`margin` as
|
|
104
|
+
* private tokens (run-cost.test.ts privateCostFieldPattern), so the version the
|
|
105
|
+
* figure was derived under stays internal (recorded only in the platform's
|
|
106
|
+
* internal raw-usage export).
|
|
107
|
+
*/
|
|
108
|
+
export const RUN_COST_BASIS_STATUSES = ["estimated", "reconciled"];
|
|
99
109
|
export function buildRunUsageSample(input) {
|
|
100
110
|
const metric = normalizeUsageSampleMetric(input.metric);
|
|
101
111
|
const expectedUnit = RUN_USAGE_SAMPLE_METRIC_UNITS[metric];
|
|
@@ -134,7 +144,9 @@ export function buildRunCostTelemetry(input) {
|
|
|
134
144
|
...(input.providerUsage ? { providerUsage: input.providerUsage.map(normalizeProviderUsage) } : {}),
|
|
135
145
|
...(input.storage ? { storage: normalizeStorage(input.storage) } : {}),
|
|
136
146
|
...(input.proxy ? { proxy: normalizeProxy(input.proxy) } : {}),
|
|
137
|
-
...(input.managedKey ? { managedKey: normalizeManagedKey(input.managedKey) } : {})
|
|
147
|
+
...(input.managedKey ? { managedKey: normalizeManagedKey(input.managedKey) } : {}),
|
|
148
|
+
...(input.billedCostUsd !== undefined ? { billedCostUsd: nonNegativeFinite(input.billedCostUsd, "billedCostUsd") } : {}),
|
|
149
|
+
...(input.costBasis ? { costBasis: normalizeCostBasis(input.costBasis) } : {})
|
|
138
150
|
});
|
|
139
151
|
}
|
|
140
152
|
export function buildRunCostTelemetryFromUsageSamples(input) {
|
|
@@ -318,6 +330,11 @@ export function mergeRunCostTelemetry(base, next) {
|
|
|
318
330
|
const storage = sumStorage(base.storage, patch.storage);
|
|
319
331
|
const proxy = sumProxy(base.proxy, patch.proxy);
|
|
320
332
|
const managedKey = mergeManagedKey(base.managedKey, patch.managedKey);
|
|
333
|
+
// Derived cost fields are LAST-WRITER-WINS (a re-derivation supersedes the
|
|
334
|
+
// prior estimate), not summed — they are projections of the whole sample set,
|
|
335
|
+
// not additive metrics.
|
|
336
|
+
const billedCostUsd = patch.billedCostUsd ?? base.billedCostUsd;
|
|
337
|
+
const costBasis = patch.costBasis ?? base.costBasis;
|
|
321
338
|
if (runId)
|
|
322
339
|
merged.runId = runId;
|
|
323
340
|
if (provider)
|
|
@@ -346,6 +363,10 @@ export function mergeRunCostTelemetry(base, next) {
|
|
|
346
363
|
merged.proxy = proxy;
|
|
347
364
|
if (managedKey)
|
|
348
365
|
merged.managedKey = managedKey;
|
|
366
|
+
if (billedCostUsd !== undefined)
|
|
367
|
+
merged.billedCostUsd = billedCostUsd;
|
|
368
|
+
if (costBasis)
|
|
369
|
+
merged.costBasis = costBasis;
|
|
349
370
|
return buildRunCostTelemetry(merged);
|
|
350
371
|
}
|
|
351
372
|
function normalizeDurations(input) {
|
|
@@ -470,6 +491,18 @@ function normalizeSummaryStatus(input) {
|
|
|
470
491
|
}
|
|
471
492
|
return input;
|
|
472
493
|
}
|
|
494
|
+
function normalizeCostBasis(input) {
|
|
495
|
+
if (!input || typeof input !== "object") {
|
|
496
|
+
throw new Error("run cost basis must be an object");
|
|
497
|
+
}
|
|
498
|
+
if (input.currency !== "USD") {
|
|
499
|
+
throw new Error(`run cost basis currency ${String(input.currency)} is not supported`);
|
|
500
|
+
}
|
|
501
|
+
if (!isStringIn(input.status, RUN_COST_BASIS_STATUSES)) {
|
|
502
|
+
throw new Error(`run cost basis status ${String(input.status)} is not supported`);
|
|
503
|
+
}
|
|
504
|
+
return Object.freeze({ currency: "USD", status: input.status });
|
|
505
|
+
}
|
|
473
506
|
function addProviderUsage(usageByKey, input, sample, field) {
|
|
474
507
|
const provider = sample.provider ?? input.provider;
|
|
475
508
|
if (!provider) {
|
|
@@ -397,7 +397,7 @@ function visitCustodyValue(input, path, findings) {
|
|
|
397
397
|
}
|
|
398
398
|
function scanStringValue(value, path, findings) {
|
|
399
399
|
for (const pattern of forbiddenStringPatterns) {
|
|
400
|
-
if (pattern
|
|
400
|
+
if (matchesForbiddenPattern(pattern, value)) {
|
|
401
401
|
findings.push(Object.freeze({
|
|
402
402
|
path,
|
|
403
403
|
reason: pattern.reason,
|
|
@@ -406,11 +406,39 @@ function scanStringValue(value, path, findings) {
|
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* A pattern fires on a value if its `regex` matches AND — when the pattern
|
|
411
|
+
* carries an `accept` predicate — at least one matched run is accepted by it.
|
|
412
|
+
* The predicate lets a shape-matched run be VETOED per-match (the entropy
|
|
413
|
+
* catch-all uses it to skip content-addressed hashes and low-entropy slugs that
|
|
414
|
+
* its coarse regex would otherwise flag); shape-only patterns have no predicate.
|
|
415
|
+
*/
|
|
416
|
+
function matchesForbiddenPattern(pattern, value) {
|
|
417
|
+
if (!pattern.accept) {
|
|
418
|
+
return pattern.regex.test(value);
|
|
419
|
+
}
|
|
420
|
+
const scan = pattern.regex.global ? pattern.regex : new RegExp(pattern.regex.source, `${pattern.regex.flags}g`);
|
|
421
|
+
scan.lastIndex = 0;
|
|
422
|
+
let match;
|
|
423
|
+
while ((match = scan.exec(value)) !== null) {
|
|
424
|
+
if (pattern.accept(match[0])) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
if (match.index === scan.lastIndex) {
|
|
428
|
+
scan.lastIndex++;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
409
433
|
const forbiddenStringPatterns = Object.freeze([
|
|
410
434
|
{ reason: "bearer_token", regex: /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/i },
|
|
411
435
|
{
|
|
412
436
|
reason: "provider_key",
|
|
413
|
-
|
|
437
|
+
// Prefixed provider keys (`sk-…`, Slack `xox*-…`, Google `AIza…`). The bare
|
|
438
|
+
// `sk-` body is intentionally generic so an unrecognised vendor's `sk-` key
|
|
439
|
+
// (e.g. DeepSeek `sk-<hex>`, OpenRouter `sk-or-…`) is still caught by shape,
|
|
440
|
+
// not left to the narrowed entropy catch-all below.
|
|
441
|
+
regex: /\b(?:sk-[A-Za-z0-9_-]{16,}|xox[baprs]-[A-Za-z0-9-]{8,}|AIza[A-Za-z0-9_-]{8,})/i
|
|
414
442
|
},
|
|
415
443
|
{ reason: "signed_url", regex: /[?&](?:X-Amz-Signature|X-Amz-Credential|X-Amz-Algorithm|AWSAccessKeyId)=/i },
|
|
416
444
|
{ reason: "object_store_key", regex: /(^|[\s"'`])(?:runs|assets)\/[^?<#\s"'`]+/i },
|
|
@@ -419,8 +447,71 @@ const forbiddenStringPatterns = Object.freeze([
|
|
|
419
447
|
reason: "private_resource_handle",
|
|
420
448
|
regex: /\b(?:machine|session|agent|file|skill|env|resource|handle|token_hash|bearer_hash)[_:-][A-Za-z0-9][A-Za-z0-9_-]{7,}\b/i
|
|
421
449
|
},
|
|
422
|
-
{
|
|
450
|
+
{
|
|
451
|
+
reason: "high_entropy_token",
|
|
452
|
+
// Catch-all for an unrecognised opaque secret blob. The candidate run
|
|
453
|
+
// EXCLUDES `_` (so `SCREAMING_SNAKE` env names and `slug_with_words` URL
|
|
454
|
+
// segments split instead of fusing into a phantom 40-char run — the live
|
|
455
|
+
// false positive was `ted_season_2_peacock_official_discussion_thread` in
|
|
456
|
+
// web-search result text and a fetched URL), and the `accept` predicate
|
|
457
|
+
// vetoes content-addressed hashes (md5/sha1/sha256 digests — the platform's
|
|
458
|
+
// OWN asset filenames) and low-entropy / single-class runs so only genuine
|
|
459
|
+
// opaque secrets remain. Slash-bearing secrets (signed URLs, connection
|
|
460
|
+
// strings, `Bearer …`) are covered by the named patterns above.
|
|
461
|
+
regex: /\b[A-Za-z0-9-]{40,}\b/,
|
|
462
|
+
accept: isHighEntropySecretRun
|
|
463
|
+
}
|
|
423
464
|
]);
|
|
465
|
+
/** A content-addressed hash (md5/sha1/sha256 hex digest) — the platform's own
|
|
466
|
+
* asset filenames and content references. Exempt from the entropy catch-all so
|
|
467
|
+
* a captured output named after its sha256 (or a hash echoed in tool-result
|
|
468
|
+
* text) is not misclassified as a leaked secret. */
|
|
469
|
+
const CONTENT_HASH_RUN = /^(?:[0-9a-f]{32}|[0-9a-f]{40}|[0-9a-f]{64})$/i;
|
|
470
|
+
/**
|
|
471
|
+
* Decide whether a coarse `[A-Za-z0-9-]{40,}` run is a genuine opaque secret.
|
|
472
|
+
* Rejects content-addressed hashes, then requires both character-class
|
|
473
|
+
* diversity (≥2 of lower/upper/digit) and high Shannon entropy — the property
|
|
474
|
+
* that separates an opaque key blob from a long dictionary-ish identifier. A
|
|
475
|
+
* real prefixless secret (base64url/alnum-mixed) clears both gates; a hash, a
|
|
476
|
+
* hyphenated slug, or a single-class run does not.
|
|
477
|
+
*/
|
|
478
|
+
function isHighEntropySecretRun(run) {
|
|
479
|
+
if (CONTENT_HASH_RUN.test(run)) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
if (!/[A-Za-z]/.test(run) || !/\d/.test(run)) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
if (highEntropyCharClassCount(run) < 2) {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
return highEntropyShannonBits(run) >= 3.0;
|
|
489
|
+
}
|
|
490
|
+
function highEntropyCharClassCount(value) {
|
|
491
|
+
let count = 0;
|
|
492
|
+
if (/[a-z]/.test(value))
|
|
493
|
+
count++;
|
|
494
|
+
if (/[A-Z]/.test(value))
|
|
495
|
+
count++;
|
|
496
|
+
if (/[0-9]/.test(value))
|
|
497
|
+
count++;
|
|
498
|
+
return count;
|
|
499
|
+
}
|
|
500
|
+
function highEntropyShannonBits(value) {
|
|
501
|
+
if (value.length === 0) {
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
const counts = new Map();
|
|
505
|
+
for (const char of value) {
|
|
506
|
+
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
507
|
+
}
|
|
508
|
+
let bits = 0;
|
|
509
|
+
for (const count of counts.values()) {
|
|
510
|
+
const p = count / value.length;
|
|
511
|
+
bits -= p * Math.log2(p);
|
|
512
|
+
}
|
|
513
|
+
return bits;
|
|
514
|
+
}
|
|
424
515
|
function isForbiddenCustodyFieldName(key) {
|
|
425
516
|
return /^(apiKey|secretValue|bearerHash|signedUrl|objectStoreKey|objectKey|vaultId|providerResponseBody|responseBody|privateResourceHandle|resourceHandle|rawBody)$/i.test(key);
|
|
426
517
|
}
|
|
@@ -210,3 +210,35 @@ export interface FileRecord {
|
|
|
210
210
|
readonly deletedAt?: string | null;
|
|
211
211
|
readonly [key: string]: unknown;
|
|
212
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Wire-level record for a workspace secret as returned by the BFF.
|
|
215
|
+
*
|
|
216
|
+
* Workspace secrets share the lifecycle SEMANTIC of skills/files: a
|
|
217
|
+
* `Secret.value(...)` is per-run and gone at terminal; PROMOTING it (or
|
|
218
|
+
* `aex.secrets.set`) persists a named, searchable workspace secret. The
|
|
219
|
+
* identity is the `name` (the handle a `Secret.ref` points at); the value
|
|
220
|
+
* rotates under that stable name, bumping `version`.
|
|
221
|
+
*
|
|
222
|
+
* This record is METADATA ONLY — it never carries the secret value. The value
|
|
223
|
+
* is write-only on create/rotate and readable solely via the audited
|
|
224
|
+
* {@link SecretReveal} path.
|
|
225
|
+
*/
|
|
226
|
+
export interface SecretRecord {
|
|
227
|
+
readonly id: string;
|
|
228
|
+
readonly name: string;
|
|
229
|
+
readonly version: number;
|
|
230
|
+
readonly state: "ready";
|
|
231
|
+
readonly createdAt?: string;
|
|
232
|
+
readonly updatedAt?: string;
|
|
233
|
+
readonly deletedAt?: string | null;
|
|
234
|
+
readonly [key: string]: unknown;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Value-bearing result of an audited `aex.secrets.reveal`. The ONLY wire shape
|
|
238
|
+
* that carries a workspace secret value back to the caller. Reveal is a logged
|
|
239
|
+
* action (POST, not GET) so a value read is always attributable.
|
|
240
|
+
*/
|
|
241
|
+
export interface SecretReveal {
|
|
242
|
+
readonly name: string;
|
|
243
|
+
readonly value: string;
|
|
244
|
+
}
|