deepline 0.1.66 → 0.1.69
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/cli/index.js +1410 -577
- package/dist/cli/index.mjs +1410 -577
- package/dist/index.d.mts +121 -1
- package/dist/index.d.ts +121 -1
- package/dist/index.js +76 -2
- package/dist/index.mjs +76 -2
- package/dist/repo/apps/play-runner-workers/src/entry.ts +122 -29
- package/dist/repo/sdk/src/client.ts +110 -0
- package/dist/repo/sdk/src/http.ts +9 -1
- package/dist/repo/sdk/src/play.ts +33 -1
- package/dist/repo/sdk/src/plays/bundle-play-file.ts +4 -1
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/sdk/src/types.ts +59 -0
- package/dist/repo/shared_libs/play-runtime/secret-capability.ts +103 -0
- package/dist/repo/shared_libs/play-runtime/secret-redaction.ts +90 -0
- package/dist/repo/shared_libs/plays/bundling/index.ts +10 -0
- package/dist/repo/shared_libs/plays/secret-guardrails.ts +57 -0
- package/package.json +1 -1
|
@@ -128,6 +128,19 @@ import {
|
|
|
128
128
|
} from '../../../shared_libs/play-runtime/csv-rename';
|
|
129
129
|
import { coordinatorRequestHeaders } from '../../../shared_libs/play-runtime/coordinator-headers';
|
|
130
130
|
import { normalizePlayRunFailure } from '../../../shared_libs/play-runtime/run-failure';
|
|
131
|
+
import { createSecretRedactionContext } from '../../../shared_libs/play-runtime/secret-redaction';
|
|
132
|
+
import {
|
|
133
|
+
assertNoSecretTaint,
|
|
134
|
+
createBearerSecretAuth,
|
|
135
|
+
createHeaderSecretAuth,
|
|
136
|
+
createSecretHandle,
|
|
137
|
+
isSecretAuth,
|
|
138
|
+
secretAuthHeaderMarkers,
|
|
139
|
+
valueContainsSecret,
|
|
140
|
+
type SecretAuth,
|
|
141
|
+
type SecretAwareRequestInit,
|
|
142
|
+
type SecretHandle,
|
|
143
|
+
} from '../../../shared_libs/play-runtime/secret-capability';
|
|
131
144
|
import type {
|
|
132
145
|
LiveNodeProgressMap,
|
|
133
146
|
LiveNodeProgressSnapshot,
|
|
@@ -3183,6 +3196,39 @@ function createMinimalWorkerCtx(
|
|
|
3183
3196
|
const inFlightChildCallsByPlayName: Record<string, number> = {};
|
|
3184
3197
|
let inFlightChildPlayCalls = 0;
|
|
3185
3198
|
const childPlaySlotWaiters: Array<() => void> = [];
|
|
3199
|
+
const secretRedactor = createSecretRedactionContext();
|
|
3200
|
+
|
|
3201
|
+
const resolveSecretAuth = async (auth?: SecretAuth) => {
|
|
3202
|
+
if (!auth) return {};
|
|
3203
|
+
const response = await fetchRuntimeApi(
|
|
3204
|
+
req.baseUrl,
|
|
3205
|
+
'/api/v2/plays/internal/runtime',
|
|
3206
|
+
{
|
|
3207
|
+
method: 'POST',
|
|
3208
|
+
headers: {
|
|
3209
|
+
authorization: `Bearer ${req.executorToken}`,
|
|
3210
|
+
'content-type': 'application/json',
|
|
3211
|
+
},
|
|
3212
|
+
body: JSON.stringify({
|
|
3213
|
+
action: 'resolve_secret',
|
|
3214
|
+
name: auth.secret.name,
|
|
3215
|
+
playName: req.playName,
|
|
3216
|
+
}),
|
|
3217
|
+
},
|
|
3218
|
+
);
|
|
3219
|
+
if (!response.ok) {
|
|
3220
|
+
throw new Error(`Secret ${auth.secret.name} is not available to this run.`);
|
|
3221
|
+
}
|
|
3222
|
+
const payload = (await response.json()) as { value?: unknown };
|
|
3223
|
+
const value = typeof payload.value === 'string' ? payload.value : null;
|
|
3224
|
+
if (!value) {
|
|
3225
|
+
throw new Error(`Secret ${auth.secret.name} is not available to this run.`);
|
|
3226
|
+
}
|
|
3227
|
+
secretRedactor.register(value);
|
|
3228
|
+
return auth.kind === 'bearer'
|
|
3229
|
+
? { authorization: `Bearer ${value}` }
|
|
3230
|
+
: { [auth.header.toLowerCase()]: value };
|
|
3231
|
+
};
|
|
3186
3232
|
|
|
3187
3233
|
const acquireChildPlaySlot = async (): Promise<() => void> => {
|
|
3188
3234
|
while (
|
|
@@ -4007,6 +4053,7 @@ function createMinimalWorkerCtx(
|
|
|
4007
4053
|
*/
|
|
4008
4054
|
signal: abortSignal ?? new AbortController().signal,
|
|
4009
4055
|
log(message: string) {
|
|
4056
|
+
assertNoSecretTaint(message, 'ctx.log');
|
|
4010
4057
|
emitEvent({
|
|
4011
4058
|
type: 'log',
|
|
4012
4059
|
level: 'info',
|
|
@@ -4184,10 +4231,11 @@ function createMinimalWorkerCtx(
|
|
|
4184
4231
|
'ctx.map(key, rows, fields, options) was removed. Use ctx.map(key, rows).step(...).run(options).',
|
|
4185
4232
|
);
|
|
4186
4233
|
},
|
|
4187
|
-
|
|
4188
|
-
|
|
4234
|
+
tools: {
|
|
4235
|
+
async execute(requestArg: unknown): Promise<unknown> {
|
|
4189
4236
|
assertNotAborted(abortSignal);
|
|
4190
4237
|
const request = normalizeToolExecuteArgs(requestArg);
|
|
4238
|
+
assertNoSecretTaint(request.input, 'ctx.tools.execute input');
|
|
4191
4239
|
return await executeWithRuntimeReceipt(
|
|
4192
4240
|
`tool:${request.id}:${deriveToolRequestIdentity({
|
|
4193
4241
|
toolId: request.toolId,
|
|
@@ -4257,9 +4305,10 @@ function createMinimalWorkerCtx(
|
|
|
4257
4305
|
timeoutMs?: number;
|
|
4258
4306
|
staleAfterSeconds?: number;
|
|
4259
4307
|
},
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4308
|
+
): Promise<unknown> {
|
|
4309
|
+
const normalizedKey = normalizeContextKey(key, 'runPlay');
|
|
4310
|
+
const resolvedName = resolvePlayRefName(playRef);
|
|
4311
|
+
assertNoSecretTaint(input, 'ctx.runPlay input');
|
|
4263
4312
|
if (!resolvedName) {
|
|
4264
4313
|
throw new Error('ctx.runPlay(...) requires a resolvable play name.');
|
|
4265
4314
|
}
|
|
@@ -4498,17 +4547,37 @@ function createMinimalWorkerCtx(
|
|
|
4498
4547
|
}
|
|
4499
4548
|
});
|
|
4500
4549
|
},
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4550
|
+
async fetch(
|
|
4551
|
+
key: string,
|
|
4552
|
+
input: string | URL,
|
|
4553
|
+
init: SecretAwareRequestInit = {},
|
|
4554
|
+
options?: { staleAfterSeconds?: number },
|
|
4555
|
+
): Promise<WorkerFetchResponse> {
|
|
4556
|
+
assertNotAborted(abortSignal);
|
|
4557
|
+
const normalizedKey = normalizeContextKey(key, 'fetch');
|
|
4558
|
+
if (
|
|
4559
|
+
valueContainsSecret(input) ||
|
|
4560
|
+
valueContainsSecret(init.body)
|
|
4561
|
+
) {
|
|
4562
|
+
throw new Error(
|
|
4563
|
+
'ctx.fetch does not allow secrets in the URL or body. Use an approved secret auth helper.',
|
|
4564
|
+
);
|
|
4565
|
+
}
|
|
4566
|
+
if (valueContainsSecret(init.headers)) {
|
|
4567
|
+
throw new Error(
|
|
4568
|
+
'ctx.fetch does not allow raw secret headers. Use ctx.secrets.bearer(...) or ctx.secrets.header(...).',
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4571
|
+
if (init.auth !== undefined && !isSecretAuth(init.auth)) {
|
|
4572
|
+
throw new Error('ctx.fetch auth must come from ctx.secrets.');
|
|
4573
|
+
}
|
|
4574
|
+
const url = input.toString();
|
|
4575
|
+
const method = (init.method ?? 'GET').toUpperCase();
|
|
4576
|
+
const secretHeaderMarkers = secretAuthHeaderMarkers(init.auth);
|
|
4577
|
+
const safeHeaders = {
|
|
4578
|
+
...normalizeFetchHeaders(init.headers),
|
|
4579
|
+
...secretHeaderMarkers,
|
|
4580
|
+
};
|
|
4512
4581
|
const body = fetchBodyIdentity(init.body);
|
|
4513
4582
|
const hasIdempotencyKey =
|
|
4514
4583
|
safeHeaders['idempotency-key'] !== undefined ||
|
|
@@ -4524,20 +4593,44 @@ function createMinimalWorkerCtx(
|
|
|
4524
4593
|
safeHeaders,
|
|
4525
4594
|
url,
|
|
4526
4595
|
})}${staleRuntimeSuffix(options?.staleAfterSeconds)}`;
|
|
4527
|
-
|
|
4528
|
-
const
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
ok: response.ok,
|
|
4533
|
-
status: response.status,
|
|
4534
|
-
statusText: response.statusText,
|
|
4535
|
-
url: response.url,
|
|
4536
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
4537
|
-
bodyText,
|
|
4538
|
-
json: parseFetchJsonOrNull(bodyText),
|
|
4596
|
+
return await executeWithRuntimeReceipt(receiptKey, async () => {
|
|
4597
|
+
const secretHeaders = await resolveSecretAuth(init.auth);
|
|
4598
|
+
const headers = {
|
|
4599
|
+
...normalizeFetchHeaders(init.headers),
|
|
4600
|
+
...secretHeaders,
|
|
4539
4601
|
};
|
|
4540
|
-
|
|
4602
|
+
const fetchInit = { ...init, headers };
|
|
4603
|
+
delete fetchInit.auth;
|
|
4604
|
+
const response = await fetch(url, fetchInit);
|
|
4605
|
+
assertNotAborted(abortSignal);
|
|
4606
|
+
const bodyText = await response.text();
|
|
4607
|
+
const redactedBodyText = secretRedactor.redactString(bodyText);
|
|
4608
|
+
return {
|
|
4609
|
+
ok: response.ok,
|
|
4610
|
+
status: response.status,
|
|
4611
|
+
statusText: response.statusText,
|
|
4612
|
+
url: response.url,
|
|
4613
|
+
headers: secretRedactor.redact(
|
|
4614
|
+
Object.fromEntries(response.headers.entries()),
|
|
4615
|
+
) as Record<string, string>,
|
|
4616
|
+
bodyText: redactedBodyText,
|
|
4617
|
+
json: secretRedactor.redact(parseFetchJsonOrNull(bodyText)),
|
|
4618
|
+
};
|
|
4619
|
+
});
|
|
4620
|
+
},
|
|
4621
|
+
secrets: {
|
|
4622
|
+
get(name: string): SecretHandle {
|
|
4623
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
4624
|
+
throw new Error('ctx.secrets.get(name) requires a non-empty name.');
|
|
4625
|
+
}
|
|
4626
|
+
return createSecretHandle(name.trim());
|
|
4627
|
+
},
|
|
4628
|
+
bearer(secret: SecretHandle): SecretAuth {
|
|
4629
|
+
return createBearerSecretAuth(secret);
|
|
4630
|
+
},
|
|
4631
|
+
header(header: string, secret: SecretHandle): SecretAuth {
|
|
4632
|
+
return createHeaderSecretAuth(header, secret);
|
|
4633
|
+
},
|
|
4541
4634
|
},
|
|
4542
4635
|
async waitForEvent(
|
|
4543
4636
|
eventType: string,
|
|
@@ -57,6 +57,9 @@ import type {
|
|
|
57
57
|
PublishPlayVersionResult,
|
|
58
58
|
StartPlayRunRequest,
|
|
59
59
|
DeletePlayResult,
|
|
60
|
+
SharePageStatus,
|
|
61
|
+
PublishSharePageRequest,
|
|
62
|
+
UpdateSharePageRequest,
|
|
60
63
|
ToolDefinition,
|
|
61
64
|
ToolSearchOptions,
|
|
62
65
|
ToolSearchResult,
|
|
@@ -158,6 +161,19 @@ export type PlaySheetRowsResult = {
|
|
|
158
161
|
deltaCursor?: number;
|
|
159
162
|
};
|
|
160
163
|
|
|
164
|
+
export type PlaySecretMetadata = {
|
|
165
|
+
_id: string;
|
|
166
|
+
orgId: string;
|
|
167
|
+
scope: 'org' | 'play';
|
|
168
|
+
playName?: string;
|
|
169
|
+
name: string;
|
|
170
|
+
status: string;
|
|
171
|
+
hasValue: boolean;
|
|
172
|
+
createdAt: number;
|
|
173
|
+
updatedAt: number;
|
|
174
|
+
lastUsedAt?: number;
|
|
175
|
+
};
|
|
176
|
+
|
|
161
177
|
export type RunsNamespace = {
|
|
162
178
|
get: (runId: string, options?: { full?: boolean }) => Promise<PlayStatus>;
|
|
163
179
|
list: (options: RunsListOptions) => Promise<PlayRunListItem[]>;
|
|
@@ -571,6 +587,30 @@ export class DeeplineClient {
|
|
|
571
587
|
};
|
|
572
588
|
}
|
|
573
589
|
|
|
590
|
+
// ——————————————————————————————————————————————————————————
|
|
591
|
+
// Secrets
|
|
592
|
+
// ——————————————————————————————————————————————————————————
|
|
593
|
+
|
|
594
|
+
async listSecrets(): Promise<PlaySecretMetadata[]> {
|
|
595
|
+
const response = await this.http.get<{ secrets?: PlaySecretMetadata[] }>(
|
|
596
|
+
'/api/v2/secrets',
|
|
597
|
+
);
|
|
598
|
+
return Array.isArray(response.secrets) ? response.secrets : [];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async checkSecret(name: string): Promise<PlaySecretMetadata | null> {
|
|
602
|
+
const normalized = name.trim().toUpperCase();
|
|
603
|
+
const secrets = await this.listSecrets();
|
|
604
|
+
return (
|
|
605
|
+
secrets.find(
|
|
606
|
+
(secret) =>
|
|
607
|
+
secret.name === normalized &&
|
|
608
|
+
secret.status === 'active' &&
|
|
609
|
+
secret.hasValue,
|
|
610
|
+
) ?? null
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
|
|
574
614
|
// ——————————————————————————————————————————————————————————
|
|
575
615
|
// Tools
|
|
576
616
|
// ——————————————————————————————————————————————————————————
|
|
@@ -1625,6 +1665,76 @@ export class DeeplineClient {
|
|
|
1625
1665
|
return this.http.delete<DeletePlayResult>(`/api/v2/plays/${encodedName}`);
|
|
1626
1666
|
}
|
|
1627
1667
|
|
|
1668
|
+
// ——————————————————————————————————————————————————————————
|
|
1669
|
+
// Plays — public share pages
|
|
1670
|
+
// ——————————————————————————————————————————————————————————
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Current share status for a play: the public page (if any), the published
|
|
1674
|
+
* copy, and the revision picker. Read-only.
|
|
1675
|
+
*/
|
|
1676
|
+
async getSharePage(name: string): Promise<SharePageStatus> {
|
|
1677
|
+
const encodedName = encodeURIComponent(name);
|
|
1678
|
+
return this.http.get<SharePageStatus>(`/api/v2/plays/${encodedName}/share`);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
/**
|
|
1682
|
+
* Publish (or repoint) the play's public share page to a revision. Requires
|
|
1683
|
+
* `acknowledgedUnlisted: true` — the page is publicly viewable. Org-admin only.
|
|
1684
|
+
*/
|
|
1685
|
+
async publishSharePage(
|
|
1686
|
+
name: string,
|
|
1687
|
+
request: PublishSharePageRequest,
|
|
1688
|
+
): Promise<SharePageStatus> {
|
|
1689
|
+
const encodedName = encodeURIComponent(name);
|
|
1690
|
+
return this.http.post<SharePageStatus>(
|
|
1691
|
+
`/api/v2/plays/${encodedName}/share`,
|
|
1692
|
+
request,
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
/**
|
|
1697
|
+
* Update share-page settings (SEO indexing, credit-cost / latency display)
|
|
1698
|
+
* without moving the published pointer. Org-admin only.
|
|
1699
|
+
*/
|
|
1700
|
+
async updateSharePage(
|
|
1701
|
+
name: string,
|
|
1702
|
+
request: UpdateSharePageRequest,
|
|
1703
|
+
): Promise<SharePageStatus> {
|
|
1704
|
+
const encodedName = encodeURIComponent(name);
|
|
1705
|
+
return this.http.patch<SharePageStatus>(
|
|
1706
|
+
`/api/v2/plays/${encodedName}/share`,
|
|
1707
|
+
request,
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* Unshare: hard-delete the play's public page and its cards. Returns the
|
|
1713
|
+
* fresh status (now `share: null`). Org-admin only. Idempotent — a no-op when
|
|
1714
|
+
* the play was never published.
|
|
1715
|
+
*/
|
|
1716
|
+
async unpublishSharePage(name: string): Promise<SharePageStatus> {
|
|
1717
|
+
const encodedName = encodeURIComponent(name);
|
|
1718
|
+
return this.http.delete<SharePageStatus>(
|
|
1719
|
+
`/api/v2/plays/${encodedName}/share`,
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* Regenerate the LLM landing-page copy for a revision (defaults to the
|
|
1725
|
+
* published one). Org-admin only.
|
|
1726
|
+
*/
|
|
1727
|
+
async regenerateSharePage(
|
|
1728
|
+
name: string,
|
|
1729
|
+
request: { revisionId?: string } = {},
|
|
1730
|
+
): Promise<SharePageStatus> {
|
|
1731
|
+
const encodedName = encodeURIComponent(name);
|
|
1732
|
+
return this.http.post<SharePageStatus>(
|
|
1733
|
+
`/api/v2/plays/${encodedName}/share/regenerate`,
|
|
1734
|
+
request,
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1628
1738
|
// ——————————————————————————————————————————————————————————
|
|
1629
1739
|
// Plays — high-level orchestration
|
|
1630
1740
|
// ——————————————————————————————————————————————————————————
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
const MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
|
|
36
36
|
|
|
37
37
|
interface RequestOptions {
|
|
38
|
-
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
38
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
39
39
|
body?: unknown;
|
|
40
40
|
formData?: FormData | (() => FormData);
|
|
41
41
|
headers?: Record<string, string>;
|
|
@@ -392,6 +392,14 @@ export class HttpClient {
|
|
|
392
392
|
});
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
async patch<T = unknown>(
|
|
396
|
+
path: string,
|
|
397
|
+
body: unknown,
|
|
398
|
+
headers?: Record<string, string>,
|
|
399
|
+
): Promise<T> {
|
|
400
|
+
return this.request<T>(path, { method: 'PATCH', body, headers });
|
|
401
|
+
}
|
|
402
|
+
|
|
395
403
|
/**
|
|
396
404
|
* Send a DELETE request.
|
|
397
405
|
*
|
|
@@ -146,6 +146,33 @@ export type PlayBindings = {
|
|
|
146
146
|
/** IANA timezone (e.g. `'America/New_York'`). Defaults to UTC. */
|
|
147
147
|
timezone?: string;
|
|
148
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* Customer-authored play secrets this play is allowed to use at runtime.
|
|
151
|
+
* Values are never bundled or exposed by the SDK; access them with
|
|
152
|
+
* `ctx.secrets.get("NAME")` and approved helpers such as
|
|
153
|
+
* `ctx.secrets.bearer(handle)`.
|
|
154
|
+
*/
|
|
155
|
+
secrets?: readonly string[];
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
declare const SECRET_HANDLE_BRAND: unique symbol;
|
|
159
|
+
|
|
160
|
+
export type SecretHandle = {
|
|
161
|
+
readonly [SECRET_HANDLE_BRAND]: never;
|
|
162
|
+
readonly name: string;
|
|
163
|
+
toString(): string;
|
|
164
|
+
toJSON(): never;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export type SecretAuth = {
|
|
168
|
+
readonly kind: 'bearer' | 'header';
|
|
169
|
+
readonly secret: SecretHandle;
|
|
170
|
+
readonly header?: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type SecretAwareRequestInit = Omit<RequestInit, 'headers'> & {
|
|
174
|
+
headers?: HeadersInit;
|
|
175
|
+
auth?: SecretAuth;
|
|
149
176
|
};
|
|
150
177
|
|
|
151
178
|
export type LoosePlayObject = {
|
|
@@ -523,7 +550,7 @@ export interface DeeplinePlayRuntimeContext {
|
|
|
523
550
|
fetch(
|
|
524
551
|
key: string,
|
|
525
552
|
url: string | URL,
|
|
526
|
-
init?:
|
|
553
|
+
init?: SecretAwareRequestInit,
|
|
527
554
|
options?: { staleAfterSeconds?: number },
|
|
528
555
|
): Promise<{
|
|
529
556
|
ok: boolean;
|
|
@@ -534,6 +561,11 @@ export interface DeeplinePlayRuntimeContext {
|
|
|
534
561
|
bodyText: string;
|
|
535
562
|
json: unknown | null;
|
|
536
563
|
}>;
|
|
564
|
+
secrets: {
|
|
565
|
+
get(name: string): SecretHandle;
|
|
566
|
+
bearer(secret: SecretHandle): SecretAuth;
|
|
567
|
+
header(header: string, secret: SecretHandle): SecretAuth;
|
|
568
|
+
};
|
|
537
569
|
runPlay<TOutput = unknown>(
|
|
538
570
|
key: string,
|
|
539
571
|
playRef: string | PlayReferenceLike,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type PlayArtifactKind,
|
|
18
18
|
} from '../../../shared_libs/play-runtime/backend.js';
|
|
19
19
|
import { resolveExecutionProfile } from '../../../shared_libs/play-runtime/profiles.js';
|
|
20
|
+
import { validatePlaySourceFilesHaveNoInlineSecrets } from '../../../shared_libs/plays/secret-guardrails.js';
|
|
20
21
|
import { discoverPackagedLocalFiles } from './local-file-discovery.js';
|
|
21
22
|
|
|
22
23
|
export type {
|
|
@@ -149,11 +150,13 @@ export async function bundlePlayFile(
|
|
|
149
150
|
filePath: string,
|
|
150
151
|
options: BundlePlayFileOptions = {},
|
|
151
152
|
): Promise<BundledPlayFileResult> {
|
|
152
|
-
|
|
153
|
+
const result = await bundlePlayFileCore(filePath, {
|
|
153
154
|
target: options.target ?? defaultPlayBundleTarget(),
|
|
154
155
|
exportName: options.exportName,
|
|
155
156
|
adapter: createSdkPlayBundlingAdapter(),
|
|
156
157
|
});
|
|
158
|
+
if (result.success) validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles);
|
|
159
|
+
return result;
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
export { PLAY_ARTIFACT_KINDS };
|
|
@@ -50,10 +50,10 @@ export type SdkRelease = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
export const SDK_RELEASE = {
|
|
53
|
-
version: '0.1.
|
|
53
|
+
version: '0.1.69',
|
|
54
54
|
apiContract: '2026-05-play-bootstrap-dataset-summary',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.69',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|
|
@@ -889,3 +889,62 @@ export interface DeletePlayResult {
|
|
|
889
889
|
deletedBindingCount: number;
|
|
890
890
|
deletedRunCount: number;
|
|
891
891
|
}
|
|
892
|
+
|
|
893
|
+
// ——————————————————————————————————————————————————————————
|
|
894
|
+
// Shareable play pages
|
|
895
|
+
// ——————————————————————————————————————————————————————————
|
|
896
|
+
|
|
897
|
+
/** Owner-facing view of a play's public share page. */
|
|
898
|
+
export interface SharePageOwnerView {
|
|
899
|
+
shareSlug: string;
|
|
900
|
+
publishedRevisionId: string;
|
|
901
|
+
publishedVersion: number;
|
|
902
|
+
visibility: string;
|
|
903
|
+
seoIndexing: 'index' | 'noindex';
|
|
904
|
+
showAverageDeeplineCost: boolean;
|
|
905
|
+
showAverageLatency: boolean;
|
|
906
|
+
/** Stable public path, e.g. `/p/{shareSlug}`. */
|
|
907
|
+
publicPath: string;
|
|
908
|
+
/** Version-pinned canonical path, e.g. `/p/{shareSlug}/v/{version}`. */
|
|
909
|
+
canonicalPath: string;
|
|
910
|
+
createdAt: number;
|
|
911
|
+
updatedAt: number;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** One row in the owner-facing revision picker for sharing. */
|
|
915
|
+
export interface SharePageRevisionOption {
|
|
916
|
+
revisionId: string;
|
|
917
|
+
version: number;
|
|
918
|
+
isLive: boolean;
|
|
919
|
+
isWorking: boolean;
|
|
920
|
+
isPublished: boolean;
|
|
921
|
+
hasMap: boolean;
|
|
922
|
+
hasCard: boolean;
|
|
923
|
+
createdAt: number;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/** Status payload from `GET/POST/PATCH /api/v2/plays/:name/share`. */
|
|
927
|
+
export interface SharePageStatus {
|
|
928
|
+
playName: string;
|
|
929
|
+
share: SharePageOwnerView | null;
|
|
930
|
+
publishedCopy: unknown | null;
|
|
931
|
+
revisions: SharePageRevisionOption[];
|
|
932
|
+
/** Present on publish responses when share-card generation was non-strict. */
|
|
933
|
+
warning?: string | null;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export interface PublishSharePageRequest {
|
|
937
|
+
/** The revision to publish/repoint the public page to. */
|
|
938
|
+
revisionId: string;
|
|
939
|
+
/** Must be true — acknowledges the page is publicly viewable. */
|
|
940
|
+
acknowledgedUnlisted: true;
|
|
941
|
+
showAverageDeeplineCost?: boolean;
|
|
942
|
+
showAverageLatency?: boolean;
|
|
943
|
+
seoIndexing?: 'index' | 'noindex';
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
export interface UpdateSharePageRequest {
|
|
947
|
+
showAverageDeeplineCost?: boolean;
|
|
948
|
+
showAverageLatency?: boolean;
|
|
949
|
+
seoIndexing?: 'index' | 'noindex';
|
|
950
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const SECRET_HANDLE_BRAND = Symbol.for('deepline.secret.handle');
|
|
2
|
+
const SECRET_AUTH_BRAND = Symbol.for('deepline.secret.auth');
|
|
3
|
+
const SECRET_HANDLE_MARKER_RE = /\[secret:[A-Z0-9_ -]+\]/i;
|
|
4
|
+
|
|
5
|
+
export type SecretHandle = {
|
|
6
|
+
readonly [SECRET_HANDLE_BRAND]: true;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
toString(): string;
|
|
9
|
+
toJSON(): never;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type SecretAuth =
|
|
13
|
+
| {
|
|
14
|
+
readonly [SECRET_AUTH_BRAND]: true;
|
|
15
|
+
readonly kind: 'bearer';
|
|
16
|
+
readonly secret: SecretHandle;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
readonly [SECRET_AUTH_BRAND]: true;
|
|
20
|
+
readonly kind: 'header';
|
|
21
|
+
readonly header: string;
|
|
22
|
+
readonly secret: SecretHandle;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type SecretAwareRequestInit = RequestInit & {
|
|
26
|
+
auth?: SecretAuth;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function isRecord(value: unknown): value is Record<string | symbol, unknown> {
|
|
30
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isSecretHandle(value: unknown): value is SecretHandle {
|
|
34
|
+
return isRecord(value) && value[SECRET_HANDLE_BRAND] === true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isSecretAuth(value: unknown): value is SecretAuth {
|
|
38
|
+
return isRecord(value) && value[SECRET_AUTH_BRAND] === true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function valueContainsSecret(value: unknown): boolean {
|
|
42
|
+
if (isSecretHandle(value) || isSecretAuth(value)) return true;
|
|
43
|
+
if (typeof value === 'string') return SECRET_HANDLE_MARKER_RE.test(value);
|
|
44
|
+
if (Array.isArray(value)) return value.some(valueContainsSecret);
|
|
45
|
+
if (isRecord(value)) return Object.values(value).some(valueContainsSecret);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createSecretHandle(name: string): SecretHandle {
|
|
50
|
+
return {
|
|
51
|
+
[SECRET_HANDLE_BRAND]: true,
|
|
52
|
+
name,
|
|
53
|
+
toString: () => `[secret:${name}]`,
|
|
54
|
+
toJSON: () => {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Secret ${name} cannot be serialized. Use an approved ctx.secrets helper.`,
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createBearerSecretAuth(secret: SecretHandle): SecretAuth {
|
|
63
|
+
if (!isSecretHandle(secret)) {
|
|
64
|
+
throw new Error('ctx.secrets.bearer(...) requires a SecretHandle.');
|
|
65
|
+
}
|
|
66
|
+
return { [SECRET_AUTH_BRAND]: true, kind: 'bearer', secret };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createHeaderSecretAuth(
|
|
70
|
+
header: string,
|
|
71
|
+
secret: SecretHandle,
|
|
72
|
+
): SecretAuth {
|
|
73
|
+
if (!isSecretHandle(secret)) {
|
|
74
|
+
throw new Error('ctx.secrets.header(...) requires a SecretHandle.');
|
|
75
|
+
}
|
|
76
|
+
if (typeof header !== 'string' || !header.trim()) {
|
|
77
|
+
throw new Error('ctx.secrets.header(...) requires a header name.');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
[SECRET_AUTH_BRAND]: true,
|
|
81
|
+
kind: 'header',
|
|
82
|
+
header: header.trim(),
|
|
83
|
+
secret,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function secretAuthHeaderMarkers(
|
|
88
|
+
auth: SecretAuth | undefined,
|
|
89
|
+
): Record<string, string> {
|
|
90
|
+
if (!auth) return {};
|
|
91
|
+
if (auth.kind === 'bearer') {
|
|
92
|
+
return { authorization: `[secret:${auth.secret.name}]` };
|
|
93
|
+
}
|
|
94
|
+
return { [auth.header.toLowerCase()]: `[secret:${auth.secret.name}]` };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function assertNoSecretTaint(value: unknown, sink: string): void {
|
|
98
|
+
if (valueContainsSecret(value)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`${sink} cannot receive secret handles or secret-tainted values. Use an approved ctx.secrets helper.`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|