@onesi/genesis 0.2.3 → 0.2.4
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/boxes.d.ts +1 -1
- package/dist/boxes.js +1 -1
- package/dist/client.d.ts +8 -1
- package/dist/client.js +43 -6
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +30 -1
- package/dist/foundry.d.ts +3 -2
- package/dist/foundry.js +3 -2
- package/dist/generated/operations.js +4 -3
- package/dist/generated/schema.d.ts +14 -11
- package/dist/genesis.d.ts +1 -1
- package/dist/genesis.openapi.json +27 -13
- package/dist/idempotency.js +6 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/redact.d.ts +0 -6
- package/dist/redact.js +3 -2
- package/dist/urls.d.ts +9 -0
- package/dist/urls.js +61 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/boxes.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { OperationInput as In } from './types.js';
|
|
|
3
3
|
/** Boxes (mail, docs, files, sheets, slides, calendar, identity, MCP): one method per operation, grouped as the
|
|
4
4
|
* Boxes resource tree. Path parameters come first, then the body, then query options, then an optional
|
|
5
5
|
* idempotency key on the operations that take one. Operations that stream or download bytes return the raw Response;
|
|
6
|
-
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write. Generated from the Boxes spec. */
|
|
6
|
+
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write; mail domain administration needs boxes:admin. Generated from the Boxes spec. */
|
|
7
7
|
export declare function boxesModule(client: GenesisClient): {
|
|
8
8
|
mail: {
|
|
9
9
|
addresses: {
|
package/dist/boxes.js
CHANGED
|
@@ -2,7 +2,7 @@ import { idempotencyHeaders } from './idempotency.js';
|
|
|
2
2
|
/** Boxes (mail, docs, files, sheets, slides, calendar, identity, MCP): one method per operation, grouped as the
|
|
3
3
|
* Boxes resource tree. Path parameters come first, then the body, then query options, then an optional
|
|
4
4
|
* idempotency key on the operations that take one. Operations that stream or download bytes return the raw Response;
|
|
5
|
-
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write. Generated from the Boxes spec. */
|
|
5
|
+
* uploads take a FormData body. GET needs boxes:read, everything else boxes:write; mail domain administration needs boxes:admin. Generated from the Boxes spec. */
|
|
6
6
|
export function boxesModule(client) {
|
|
7
7
|
return {
|
|
8
8
|
mail: {
|
package/dist/client.d.ts
CHANGED
|
@@ -25,8 +25,15 @@ export interface GenesisOptions {
|
|
|
25
25
|
headers?: Record<string, string>;
|
|
26
26
|
userAgent?: string;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Redact URL userinfo from an error's message, stack and cause chain (bounded). If the error is frozen or any of
|
|
30
|
+
* those properties cannot be rewritten, return a new sanitized Error instead of the original.
|
|
31
|
+
*/
|
|
32
|
+
export declare function scrubError(e: unknown, depth?: number): unknown;
|
|
28
33
|
export declare class GenesisClient {
|
|
29
|
-
|
|
34
|
+
/** The configured gateway root, kept without userinfo so it is safe to log or print. */
|
|
35
|
+
get baseUrl(): string;
|
|
36
|
+
private readonly root;
|
|
30
37
|
readonly routing: 'gateway' | 'direct';
|
|
31
38
|
private readonly opts;
|
|
32
39
|
private readonly fetchImpl;
|
package/dist/client.js
CHANGED
|
@@ -8,7 +8,7 @@ import { blindsOperationKey, blindsOwnerTransport, isBlindsOwnerOperation } from
|
|
|
8
8
|
import { isHivesObjectUpload, hivesObjectBody, hivesContentType, isHivesResourceOperation, resourceError, resourceErrorFromResponse } from './hives-resources-transport.js';
|
|
9
9
|
import { operationTable, services } from './generated/operations.js';
|
|
10
10
|
import { GenesisError } from './errors.js';
|
|
11
|
-
import { requireSecureUrl } from './urls.js';
|
|
11
|
+
import { requireSecureUrl, redactUserinfo, stripUserinfo } from './urls.js';
|
|
12
12
|
import { iamDirectoryFailure, isIamDirectoryOperation } from './iam-directory.js';
|
|
13
13
|
import { validateHivesInvocation, hivesFunctionResponseHeaders, hivesFunctionMetadata, hivesOperationError } from './hives-functions.js';
|
|
14
14
|
import { validateHivesEnvironment, validateHivesEnvironmentNames, hivesEnvironmentSetNames, hivesEnvironmentMetadata, hivesEnvironmentReveal } from './hives-environment.js';
|
|
@@ -21,20 +21,57 @@ const ENDLESS_STREAMS = {
|
|
|
21
21
|
hives_stream_logs: 'genesis.hives.logs.stream() or genesis.hives.logs.window()',
|
|
22
22
|
hives_stream_deployment_logs: 'hivesSseWindow(await genesis.raw(...))',
|
|
23
23
|
};
|
|
24
|
+
const SCRUB_DEPTH = 5;
|
|
25
|
+
/**
|
|
26
|
+
* Redact URL userinfo from an error's message, stack and cause chain (bounded). If the error is frozen or any of
|
|
27
|
+
* those properties cannot be rewritten, return a new sanitized Error instead of the original.
|
|
28
|
+
*/
|
|
29
|
+
export function scrubError(e, depth = 0) {
|
|
30
|
+
if (typeof e === 'string')
|
|
31
|
+
return redactUserinfo(e);
|
|
32
|
+
if (!(e instanceof Error))
|
|
33
|
+
return e;
|
|
34
|
+
if (depth >= SCRUB_DEPTH)
|
|
35
|
+
return undefined;
|
|
36
|
+
const message = redactUserinfo(String(e.message));
|
|
37
|
+
const stack = typeof e.stack === 'string' ? redactUserinfo(e.stack) : e.stack;
|
|
38
|
+
const hasCause = 'cause' in e;
|
|
39
|
+
const cause = hasCause ? scrubError(e.cause, depth + 1) : undefined;
|
|
40
|
+
if (message === e.message && stack === e.stack && cause === e.cause)
|
|
41
|
+
return e;
|
|
42
|
+
try {
|
|
43
|
+
if (Object.isFrozen(e))
|
|
44
|
+
throw e;
|
|
45
|
+
Object.defineProperty(e, 'message', { value: message, writable: true, configurable: true, enumerable: false });
|
|
46
|
+
Object.defineProperty(e, 'stack', { value: stack, writable: true, configurable: true, enumerable: false });
|
|
47
|
+
if (hasCause)
|
|
48
|
+
Object.defineProperty(e, 'cause', { value: cause, writable: true, configurable: true, enumerable: false });
|
|
49
|
+
if (e.message === message && e.stack === stack && (!hasCause || e.cause === cause))
|
|
50
|
+
return e;
|
|
51
|
+
}
|
|
52
|
+
catch { /* fall through to a fresh error */ }
|
|
53
|
+
const fresh = new (e instanceof TypeError ? TypeError : Error)(message, hasCause ? { cause } : undefined);
|
|
54
|
+
fresh.name = redactUserinfo(String(e.name));
|
|
55
|
+
fresh.stack = stack;
|
|
56
|
+
return fresh;
|
|
57
|
+
}
|
|
24
58
|
export class GenesisClient {
|
|
25
|
-
|
|
59
|
+
/** The configured gateway root, kept without userinfo so it is safe to log or print. */
|
|
60
|
+
get baseUrl() { return stripUserinfo(this.root); }
|
|
61
|
+
root;
|
|
26
62
|
routing;
|
|
27
63
|
opts;
|
|
28
64
|
fetchImpl;
|
|
29
65
|
get contextFetch() { return this.fetchImpl; }
|
|
30
66
|
constructor(opts = {}) {
|
|
31
67
|
this.opts = opts;
|
|
32
|
-
this.
|
|
68
|
+
this.root = requireSecureUrl(opts.baseUrl ?? env.GENESIS_BASE_URL ?? DEFAULT_BASE_URL, 'baseUrl').replace(/\/+$/, '');
|
|
33
69
|
this.routing = opts.routing ?? (env.GENESIS_ROUTING === 'direct' ? 'direct' : 'gateway');
|
|
34
70
|
const f = opts.fetch ?? globalThis.fetch;
|
|
35
71
|
if (!f)
|
|
36
72
|
throw new Error('No fetch available; pass options.fetch');
|
|
37
|
-
|
|
73
|
+
// fetch errors can quote the request URL (and its userinfo); scrub them before they surface.
|
|
74
|
+
this.fetchImpl = ((input, init) => f.call(globalThis, input, init).catch((e) => { throw scrubError(e); }));
|
|
38
75
|
}
|
|
39
76
|
static operations = operationTable;
|
|
40
77
|
static services = services;
|
|
@@ -68,7 +105,7 @@ export class GenesisClient {
|
|
|
68
105
|
p = fill(op.upstream.path);
|
|
69
106
|
}
|
|
70
107
|
else {
|
|
71
|
-
base = this.
|
|
108
|
+
base = this.root;
|
|
72
109
|
p = fill(op.path);
|
|
73
110
|
}
|
|
74
111
|
const qs = new URLSearchParams();
|
|
@@ -189,7 +226,7 @@ export class GenesisClient {
|
|
|
189
226
|
const url = this.url(operationId, input);
|
|
190
227
|
const headers = {
|
|
191
228
|
accept: 'application/json',
|
|
192
|
-
'user-agent': this.opts.userAgent ?? '@onesi/genesis/0.2.
|
|
229
|
+
'user-agent': this.opts.userAgent ?? '@onesi/genesis/0.2.4',
|
|
193
230
|
...this.opts.headers, ...init.headers,
|
|
194
231
|
};
|
|
195
232
|
if (op.service === 'blinds' && !['GET', 'HEAD', 'OPTIONS'].includes(op.method))
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** An error body is read to at most this many bytes, before any parsing or redaction; the rest is dropped. */
|
|
2
|
+
export declare const ERROR_BODY_READ_LIMIT: number;
|
|
3
|
+
/** Read at most `limit` bytes of a response body as text, cancelling the stream once the limit is reached. */
|
|
4
|
+
export declare function readCappedText(res: Response, limit?: number): Promise<string>;
|
|
1
5
|
/** Normalized error for every Genesis service (gateway `{error:{type,message}}`, IAM `{error,error_description}`, Relays `{error}`, Foundry/OpenAI shape). */
|
|
2
6
|
export declare class GenesisError extends Error {
|
|
3
7
|
readonly status: number;
|
package/dist/errors.js
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { authorizationCode, redactErrorBody } from './redact.js';
|
|
2
|
+
/** An error body is read to at most this many bytes, before any parsing or redaction; the rest is dropped. */
|
|
3
|
+
export const ERROR_BODY_READ_LIMIT = 64 * 1024;
|
|
4
|
+
/** Read at most `limit` bytes of a response body as text, cancelling the stream once the limit is reached. */
|
|
5
|
+
export async function readCappedText(res, limit = ERROR_BODY_READ_LIMIT) {
|
|
6
|
+
const reader = res.body?.getReader();
|
|
7
|
+
if (!reader)
|
|
8
|
+
return (await res.text()).slice(0, limit);
|
|
9
|
+
const decoder = new TextDecoder();
|
|
10
|
+
let text = '';
|
|
11
|
+
let bytes = 0;
|
|
12
|
+
try {
|
|
13
|
+
for (;;) {
|
|
14
|
+
const { done, value } = await reader.read();
|
|
15
|
+
if (done)
|
|
16
|
+
break;
|
|
17
|
+
const room = limit - bytes;
|
|
18
|
+
text += decoder.decode(value.byteLength > room ? value.subarray(0, room) : value, { stream: true });
|
|
19
|
+
bytes += Math.min(value.byteLength, room);
|
|
20
|
+
if (bytes >= limit) {
|
|
21
|
+
void reader.cancel().catch(() => { });
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return text + decoder.decode();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
reader.releaseLock?.();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
2
31
|
/** Normalized error for every Genesis service (gateway `{error:{type,message}}`, IAM `{error,error_description}`, Relays `{error}`, Foundry/OpenAI shape). */
|
|
3
32
|
export class GenesisError extends Error {
|
|
4
33
|
status;
|
|
@@ -16,7 +45,7 @@ export class GenesisError extends Error {
|
|
|
16
45
|
this.body = init.body;
|
|
17
46
|
}
|
|
18
47
|
static async fromResponse(res, operationId) {
|
|
19
|
-
const text = await res
|
|
48
|
+
const text = await readCappedText(res).catch(() => '');
|
|
20
49
|
let body = text;
|
|
21
50
|
try {
|
|
22
51
|
body = text ? JSON.parse(text) : undefined;
|
package/dist/foundry.d.ts
CHANGED
|
@@ -266,7 +266,8 @@ export declare function foundryModule(client: GenesisClient): {
|
|
|
266
266
|
[key: string]: unknown;
|
|
267
267
|
}>;
|
|
268
268
|
};
|
|
269
|
-
/** OpenRouter-style generation lookup by request id
|
|
269
|
+
/** OpenRouter-style generation lookup by request id, retained content and feedback for your own generations (not billed).
|
|
270
|
+
* Feedback requires an idempotency key (8-128 characters of A-Z a-z 0-9 _ . : -); reuse it to retry. */
|
|
270
271
|
generation: {
|
|
271
272
|
get: (id: string) => Promise<{
|
|
272
273
|
data: {
|
|
@@ -286,7 +287,7 @@ export declare function foundryModule(client: GenesisClient): {
|
|
|
286
287
|
content: (id: string) => Promise<{
|
|
287
288
|
[key: string]: unknown;
|
|
288
289
|
}>;
|
|
289
|
-
feedback: (body: In<"foundry_submitGenerationFeedback">["body"]) => Promise<{
|
|
290
|
+
feedback: (body: In<"foundry_submitGenerationFeedback">["body"], idempotencyKey: string) => Promise<{
|
|
290
291
|
[key: string]: unknown;
|
|
291
292
|
}>;
|
|
292
293
|
};
|
package/dist/foundry.js
CHANGED
|
@@ -85,11 +85,12 @@ export function foundryModule(client) {
|
|
|
85
85
|
status: (workload_id) => client.request('foundry_getWorkloadStatus', { workload_id }),
|
|
86
86
|
terminate: (workload_id) => client.request('foundry_terminateWorkload', { workload_id }),
|
|
87
87
|
},
|
|
88
|
-
/** OpenRouter-style generation lookup by request id
|
|
88
|
+
/** OpenRouter-style generation lookup by request id, retained content and feedback for your own generations (not billed).
|
|
89
|
+
* Feedback requires an idempotency key (8-128 characters of A-Z a-z 0-9 _ . : -); reuse it to retry. */
|
|
89
90
|
generation: {
|
|
90
91
|
get: (id) => client.request('foundry_getGeneration', { id }),
|
|
91
92
|
content: (id) => client.request('foundry_getGenerationContent', { id }),
|
|
92
|
-
feedback: (body) => client.request('foundry_submitGenerationFeedback', { body }),
|
|
93
|
+
feedback: async (body, idempotencyKey) => client.request('foundry_submitGenerationFeedback', { body }, { headers: idempotencyHeaders('foundry_submitGenerationFeedback', idempotencyKey) }),
|
|
93
94
|
},
|
|
94
95
|
batches: {
|
|
95
96
|
create: (body) => client.request('foundry_createBatch', { body }),
|
|
@@ -2995,7 +2995,7 @@ export const operationTable = {
|
|
|
2995
2995
|
"method": "GET",
|
|
2996
2996
|
"path": "/api/v1/generation/content"
|
|
2997
2997
|
},
|
|
2998
|
-
"mcp":
|
|
2998
|
+
"mcp": true,
|
|
2999
2999
|
"internal": false,
|
|
3000
3000
|
"public": false,
|
|
3001
3001
|
"pathParams": [],
|
|
@@ -3026,13 +3026,14 @@ export const operationTable = {
|
|
|
3026
3026
|
"method": "POST",
|
|
3027
3027
|
"path": "/api/v1/generation/feedback"
|
|
3028
3028
|
},
|
|
3029
|
-
"mcp":
|
|
3029
|
+
"mcp": true,
|
|
3030
3030
|
"internal": false,
|
|
3031
3031
|
"public": false,
|
|
3032
3032
|
"pathParams": [],
|
|
3033
3033
|
"queryParams": [],
|
|
3034
3034
|
"hasBody": true,
|
|
3035
3035
|
"requestContentType": "application/json",
|
|
3036
|
+
"idempotencyKey": "required",
|
|
3036
3037
|
"inputSchema": {
|
|
3037
3038
|
"type": "object",
|
|
3038
3039
|
"properties": {
|
|
@@ -3158,7 +3159,7 @@ export const operationTable = {
|
|
|
3158
3159
|
"method": "POST",
|
|
3159
3160
|
"path": "/api/v1/analytics/query"
|
|
3160
3161
|
},
|
|
3161
|
-
"mcp":
|
|
3162
|
+
"mcp": true,
|
|
3162
3163
|
"internal": false,
|
|
3163
3164
|
"public": false,
|
|
3164
3165
|
"pathParams": [],
|
|
@@ -1023,7 +1023,7 @@ export interface paths {
|
|
|
1023
1023
|
};
|
|
1024
1024
|
/**
|
|
1025
1025
|
* Get retained generation content
|
|
1026
|
-
* @description
|
|
1026
|
+
* @description Retained prompt and output for one of your own generations (`id`), scoped to your organization. Not billed. Foundry admits `foundry:gateway:invoke` or management (openrouter_generations.rs authenticate_invoke_or_management): a Console login session gets the delegated `foundry:gateway:invoke` child, and an organization API key needs the `foundry:invoke` permission. 404 when the id is not yours; 403 `content_not_retained` when Foundry kept no content for it, 410 `content_expired` once retention ends.
|
|
1027
1027
|
*/
|
|
1028
1028
|
get: operations["foundry_getGenerationContent"];
|
|
1029
1029
|
put?: never;
|
|
@@ -1045,7 +1045,7 @@ export interface paths {
|
|
|
1045
1045
|
put?: never;
|
|
1046
1046
|
/**
|
|
1047
1047
|
* Submit idempotent generation feedback
|
|
1048
|
-
* @description Requires
|
|
1048
|
+
* @description Record feedback on one of your own generations (`generation_id`, `category`, optional `comment` up to 1000 characters). Requires an `Idempotency-Key` (8-128 characters of A-Z a-z 0-9 - _ . :): a retry with the same key and body is answered as a success without recording twice, and the same key with a different body is a 409. Not billed. Foundry admits `foundry:gateway:invoke` or management (openrouter_generations.rs authenticate_invoke_or_management): a Console login session gets the delegated `foundry:gateway:invoke` child, and an organization API key needs the `foundry:invoke` permission.
|
|
1049
1049
|
*/
|
|
1050
1050
|
post: operations["foundry_submitGenerationFeedback"];
|
|
1051
1051
|
delete?: never;
|
|
@@ -1125,7 +1125,7 @@ export interface paths {
|
|
|
1125
1125
|
put?: never;
|
|
1126
1126
|
/**
|
|
1127
1127
|
* Query tenant analytics
|
|
1128
|
-
* @description
|
|
1128
|
+
* @description Aggregate request-log analytics for your own organization (all projects and environments), served by Foundry's native reporting receiver (POST /v1/analytics/query). Read-only and not billed. The query body is bounded (64 KiB) and validated by Foundry, and the result is bound to the caller's organization. Works with a Console login session or an organization API key holding `foundry:read`; either way the gateway delegates a one-scope `foundry:reporting:read` child. gateway/src/foundry-reporting-read.ts.
|
|
1129
1129
|
*/
|
|
1130
1130
|
post: operations["foundry_queryAnalytics"];
|
|
1131
1131
|
delete?: never;
|
|
@@ -1974,7 +1974,7 @@ export interface paths {
|
|
|
1974
1974
|
};
|
|
1975
1975
|
/**
|
|
1976
1976
|
* Read your organization's inference request logs
|
|
1977
|
-
* @description
|
|
1977
|
+
* @description Console login session or an organization API key holding `foundry:read` (a one-scope `foundry:reporting:read` child, Sandbox). Read-only, newest first; the gateway rejects any query parameter other than the ones below and returns only the listed fields. gateway/src/foundry-reporting-read.ts.
|
|
1978
1978
|
*/
|
|
1979
1979
|
get: operations["foundry_listRequestLogs"];
|
|
1980
1980
|
put?: never;
|
|
@@ -1994,7 +1994,7 @@ export interface paths {
|
|
|
1994
1994
|
};
|
|
1995
1995
|
/**
|
|
1996
1996
|
* Summarise your provider credentials and credential policies
|
|
1997
|
-
* @description
|
|
1997
|
+
* @description Console login session or an organization API key holding `foundry:read` (a one-scope `foundry:reporting:read` child, Sandbox). Configuration metadata only: provider ids, enabled flags, credential modes and timestamps. No secret material, and no health or execution verification. Takes no query. gateway/src/foundry-reporting-read.ts.
|
|
1998
1998
|
*/
|
|
1999
1999
|
get: operations["foundry_getProviderCredentialSummary"];
|
|
2000
2000
|
put?: never;
|
|
@@ -20652,7 +20652,7 @@ export interface paths {
|
|
|
20652
20652
|
put?: never;
|
|
20653
20653
|
/**
|
|
20654
20654
|
* Reserve a mail address on a verified custom domain
|
|
20655
|
-
* @description Requires
|
|
20655
|
+
* @description Requires the boxes:admin key scope. domainId is required in the body (422 otherwise). The domain must be verified (or, with the resend provider, ready for sending and receiving) and the local part must not already be reserved.
|
|
20656
20656
|
*/
|
|
20657
20657
|
post: operations["boxes_mail_addresses_create"];
|
|
20658
20658
|
delete?: never;
|
|
@@ -20672,7 +20672,7 @@ export interface paths {
|
|
|
20672
20672
|
put?: never;
|
|
20673
20673
|
/**
|
|
20674
20674
|
* Reserve a mail address on a specific domain
|
|
20675
|
-
* @description Requires
|
|
20675
|
+
* @description Requires the boxes:admin key scope. Same behavior as POST /v1/mail/addresses but the domain comes from the path; any domainId in the body is ignored.
|
|
20676
20676
|
*/
|
|
20677
20677
|
post: operations["boxes_mail_domains_addresses_create"];
|
|
20678
20678
|
delete?: never;
|
|
@@ -20690,13 +20690,13 @@ export interface paths {
|
|
|
20690
20690
|
};
|
|
20691
20691
|
/**
|
|
20692
20692
|
* List custom mail domains
|
|
20693
|
-
* @description Requires
|
|
20693
|
+
* @description Requires the boxes:admin key scope.
|
|
20694
20694
|
*/
|
|
20695
20695
|
get: operations["boxes_mail_domains_list"];
|
|
20696
20696
|
put?: never;
|
|
20697
20697
|
/**
|
|
20698
20698
|
* Register a custom mail domain
|
|
20699
|
-
* @description Requires
|
|
20699
|
+
* @description Requires the boxes:admin key scope. Returns the DNS records to publish before calling verify.
|
|
20700
20700
|
*/
|
|
20701
20701
|
post: operations["boxes_mail_domains_create"];
|
|
20702
20702
|
delete?: never;
|
|
@@ -20716,7 +20716,7 @@ export interface paths {
|
|
|
20716
20716
|
put?: never;
|
|
20717
20717
|
/**
|
|
20718
20718
|
* Check DNS and verify a mail domain
|
|
20719
|
-
* @description Requires
|
|
20719
|
+
* @description Requires the boxes:admin key scope. A domain becomes verified only when every expected record passes. The meta block is present only with the built-in verifier, not with the resend provider.
|
|
20720
20720
|
*/
|
|
20721
20721
|
post: operations["boxes_mail_domains_verify"];
|
|
20722
20722
|
delete?: never;
|
|
@@ -41512,7 +41512,10 @@ export interface operations {
|
|
|
41512
41512
|
foundry_submitGenerationFeedback: {
|
|
41513
41513
|
parameters: {
|
|
41514
41514
|
query?: never;
|
|
41515
|
-
header
|
|
41515
|
+
header: {
|
|
41516
|
+
/** @description Stable key, 8-128 characters of A-Z a-z 0-9 - _ . :; Foundry refuses feedback without one (400). Reuse it to retry. */
|
|
41517
|
+
"Idempotency-Key": string;
|
|
41518
|
+
};
|
|
41516
41519
|
path?: never;
|
|
41517
41520
|
cookie?: never;
|
|
41518
41521
|
};
|
package/dist/genesis.d.ts
CHANGED
|
@@ -8917,7 +8917,7 @@ export declare class Genesis extends GenesisClient {
|
|
|
8917
8917
|
content: (id: string) => Promise<{
|
|
8918
8918
|
[key: string]: unknown;
|
|
8919
8919
|
}>;
|
|
8920
|
-
feedback: (body: In<"foundry_submitGenerationFeedback">["body"]) => Promise<{
|
|
8920
|
+
feedback: (body: In<"foundry_submitGenerationFeedback">["body"], idempotencyKey: string) => Promise<{
|
|
8921
8921
|
[key: string]: unknown;
|
|
8922
8922
|
}>;
|
|
8923
8923
|
};
|
|
@@ -2783,7 +2783,7 @@
|
|
|
2783
2783
|
}
|
|
2784
2784
|
}
|
|
2785
2785
|
],
|
|
2786
|
-
"description": "
|
|
2786
|
+
"description": "Retained prompt and output for one of your own generations (`id`), scoped to your organization. Not billed. Foundry admits `foundry:gateway:invoke` or management (openrouter_generations.rs authenticate_invoke_or_management): a Console login session gets the delegated `foundry:gateway:invoke` child, and an organization API key needs the `foundry:invoke` permission. 404 when the id is not yours; 403 `content_not_retained` when Foundry kept no content for it, 410 `content_expired` once retention ends.",
|
|
2787
2787
|
"x-genesis-service": "foundry",
|
|
2788
2788
|
"x-upstream": {
|
|
2789
2789
|
"base": "https://api.fdy.sh",
|
|
@@ -2791,7 +2791,7 @@
|
|
|
2791
2791
|
"path": "/api/v1/generation/content",
|
|
2792
2792
|
"status": "live"
|
|
2793
2793
|
},
|
|
2794
|
-
"x-mcp":
|
|
2794
|
+
"x-mcp": true
|
|
2795
2795
|
}
|
|
2796
2796
|
},
|
|
2797
2797
|
"/v1/foundry/generation/feedback": {
|
|
@@ -2829,7 +2829,21 @@
|
|
|
2829
2829
|
}
|
|
2830
2830
|
}
|
|
2831
2831
|
},
|
|
2832
|
-
"
|
|
2832
|
+
"parameters": [
|
|
2833
|
+
{
|
|
2834
|
+
"name": "Idempotency-Key",
|
|
2835
|
+
"in": "header",
|
|
2836
|
+
"required": true,
|
|
2837
|
+
"description": "Stable key, 8-128 characters of A-Z a-z 0-9 - _ . :; Foundry refuses feedback without one (400). Reuse it to retry.",
|
|
2838
|
+
"schema": {
|
|
2839
|
+
"type": "string",
|
|
2840
|
+
"minLength": 8,
|
|
2841
|
+
"maxLength": 128,
|
|
2842
|
+
"pattern": "^[A-Za-z0-9_.:-]{8,128}$"
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
],
|
|
2846
|
+
"description": "Record feedback on one of your own generations (`generation_id`, `category`, optional `comment` up to 1000 characters). Requires an `Idempotency-Key` (8-128 characters of A-Z a-z 0-9 - _ . :): a retry with the same key and body is answered as a success without recording twice, and the same key with a different body is a 409. Not billed. Foundry admits `foundry:gateway:invoke` or management (openrouter_generations.rs authenticate_invoke_or_management): a Console login session gets the delegated `foundry:gateway:invoke` child, and an organization API key needs the `foundry:invoke` permission.",
|
|
2833
2847
|
"x-genesis-service": "foundry",
|
|
2834
2848
|
"x-upstream": {
|
|
2835
2849
|
"base": "https://api.fdy.sh",
|
|
@@ -2837,7 +2851,7 @@
|
|
|
2837
2851
|
"path": "/api/v1/generation/feedback",
|
|
2838
2852
|
"status": "live"
|
|
2839
2853
|
},
|
|
2840
|
-
"x-mcp":
|
|
2854
|
+
"x-mcp": true
|
|
2841
2855
|
}
|
|
2842
2856
|
},
|
|
2843
2857
|
"/v1/foundry/activity": {
|
|
@@ -2993,7 +3007,7 @@
|
|
|
2993
3007
|
}
|
|
2994
3008
|
}
|
|
2995
3009
|
},
|
|
2996
|
-
"description": "
|
|
3010
|
+
"description": "Aggregate request-log analytics for your own organization (all projects and environments), served by Foundry's native reporting receiver (POST /v1/analytics/query). Read-only and not billed. The query body is bounded (64 KiB) and validated by Foundry, and the result is bound to the caller's organization. Works with a Console login session or an organization API key holding `foundry:read`; either way the gateway delegates a one-scope `foundry:reporting:read` child. gateway/src/foundry-reporting-read.ts.",
|
|
2997
3011
|
"x-genesis-service": "foundry",
|
|
2998
3012
|
"x-upstream": {
|
|
2999
3013
|
"base": "https://api.fdy.sh",
|
|
@@ -3001,7 +3015,7 @@
|
|
|
3001
3015
|
"path": "/api/v1/analytics/query",
|
|
3002
3016
|
"status": "live"
|
|
3003
3017
|
},
|
|
3004
|
-
"x-mcp":
|
|
3018
|
+
"x-mcp": true
|
|
3005
3019
|
}
|
|
3006
3020
|
},
|
|
3007
3021
|
"/v1/foundry/generations/{receipt_id}": {
|
|
@@ -5428,7 +5442,7 @@
|
|
|
5428
5442
|
"tags": [
|
|
5429
5443
|
"foundry"
|
|
5430
5444
|
],
|
|
5431
|
-
"description": "
|
|
5445
|
+
"description": "Console login session or an organization API key holding `foundry:read` (a one-scope `foundry:reporting:read` child, Sandbox). Read-only, newest first; the gateway rejects any query parameter other than the ones below and returns only the listed fields. gateway/src/foundry-reporting-read.ts.",
|
|
5432
5446
|
"parameters": [
|
|
5433
5447
|
{
|
|
5434
5448
|
"name": "start",
|
|
@@ -5523,7 +5537,7 @@
|
|
|
5523
5537
|
"tags": [
|
|
5524
5538
|
"foundry"
|
|
5525
5539
|
],
|
|
5526
|
-
"description": "
|
|
5540
|
+
"description": "Console login session or an organization API key holding `foundry:read` (a one-scope `foundry:reporting:read` child, Sandbox). Configuration metadata only: provider ids, enabled flags, credential modes and timestamps. No secret material, and no health or execution verification. Takes no query. gateway/src/foundry-reporting-read.ts.",
|
|
5527
5541
|
"responses": {
|
|
5528
5542
|
"200": {
|
|
5529
5543
|
"description": "Provider credential configuration summary",
|
|
@@ -82895,7 +82909,7 @@
|
|
|
82895
82909
|
"post": {
|
|
82896
82910
|
"operationId": "boxes_mail_addresses_create",
|
|
82897
82911
|
"summary": "Reserve a mail address on a verified custom domain",
|
|
82898
|
-
"description": "Requires
|
|
82912
|
+
"description": "Requires the boxes:admin key scope. domainId is required in the body (422 otherwise). The domain must be verified (or, with the resend provider, ready for sending and receiving) and the local part must not already be reserved.",
|
|
82899
82913
|
"requestBody": {
|
|
82900
82914
|
"required": true,
|
|
82901
82915
|
"content": {
|
|
@@ -83014,7 +83028,7 @@
|
|
|
83014
83028
|
"post": {
|
|
83015
83029
|
"operationId": "boxes_mail_domains_addresses_create",
|
|
83016
83030
|
"summary": "Reserve a mail address on a specific domain",
|
|
83017
|
-
"description": "Requires
|
|
83031
|
+
"description": "Requires the boxes:admin key scope. Same behavior as POST /v1/mail/addresses but the domain comes from the path; any domainId in the body is ignored.",
|
|
83018
83032
|
"parameters": [
|
|
83019
83033
|
{
|
|
83020
83034
|
"name": "domain_id",
|
|
@@ -83095,7 +83109,7 @@
|
|
|
83095
83109
|
"post": {
|
|
83096
83110
|
"operationId": "boxes_mail_domains_create",
|
|
83097
83111
|
"summary": "Register a custom mail domain",
|
|
83098
|
-
"description": "Requires
|
|
83112
|
+
"description": "Requires the boxes:admin key scope. Returns the DNS records to publish before calling verify.",
|
|
83099
83113
|
"requestBody": {
|
|
83100
83114
|
"required": true,
|
|
83101
83115
|
"content": {
|
|
@@ -83160,7 +83174,7 @@
|
|
|
83160
83174
|
"get": {
|
|
83161
83175
|
"operationId": "boxes_mail_domains_list",
|
|
83162
83176
|
"summary": "List custom mail domains",
|
|
83163
|
-
"description": "Requires
|
|
83177
|
+
"description": "Requires the boxes:admin key scope.",
|
|
83164
83178
|
"responses": {
|
|
83165
83179
|
"200": {
|
|
83166
83180
|
"description": "Domains",
|
|
@@ -83211,7 +83225,7 @@
|
|
|
83211
83225
|
"post": {
|
|
83212
83226
|
"operationId": "boxes_mail_domains_verify",
|
|
83213
83227
|
"summary": "Check DNS and verify a mail domain",
|
|
83214
|
-
"description": "Requires
|
|
83228
|
+
"description": "Requires the boxes:admin key scope. A domain becomes verified only when every expected record passes. The meta block is present only with the built-in verifier, not with the resend provider.",
|
|
83215
83229
|
"parameters": [
|
|
83216
83230
|
{
|
|
83217
83231
|
"name": "domain_id",
|
package/dist/idempotency.js
CHANGED
|
@@ -2,7 +2,7 @@ import { GenesisError } from './errors.js';
|
|
|
2
2
|
import { operationTable } from './generated/operations.js';
|
|
3
3
|
const REQUIRED = /^[\x21-\x7e]{8,128}$/;
|
|
4
4
|
const OPTIONAL = /^[\x21-\x7e]{1,256}$/;
|
|
5
|
-
/** Auto's Idempotency-Key grammar (l1felabs/auto src/operating.ts IDEMPOTENCY_KEY). */
|
|
5
|
+
/** Auto's Idempotency-Key grammar (l1felabs/auto src/operating.ts IDEMPOTENCY_KEY); Foundry feedback uses the same. */
|
|
6
6
|
const AUTO_KEY = /^[A-Za-z0-9_.:-]{8,128}$/;
|
|
7
7
|
/** The Idempotency-Key header an operation's spec declares. A required key is refused locally when missing
|
|
8
8
|
* or malformed, before any request is sent; reuse the same key to replay instead of acting twice. */
|
|
@@ -25,6 +25,11 @@ export function idempotencyHeaders(operationId, key) {
|
|
|
25
25
|
throw new GenesisError({ status: 0, type: 'invalid_request', operationId,
|
|
26
26
|
message: `${operationId} idempotency key must be 8-128 characters of A-Z a-z 0-9 _ . : -` });
|
|
27
27
|
}
|
|
28
|
+
// Foundry generation feedback takes [A-Za-z0-9_.:-] only (openrouter_generations.rs validate_feedback).
|
|
29
|
+
if (operationId === 'foundry_submitGenerationFeedback' && !AUTO_KEY.test(key)) {
|
|
30
|
+
throw new GenesisError({ status: 0, type: 'invalid_request', operationId,
|
|
31
|
+
message: `${operationId} idempotency key must be 8-128 characters of A-Z a-z 0-9 _ . : -` });
|
|
32
|
+
}
|
|
28
33
|
return { 'idempotency-key': key };
|
|
29
34
|
}
|
|
30
35
|
/** The If-Match header an operation's spec requires (Auto's optimistic concurrency): the resource `version`
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { iamDirectoryOperations, isIamDirectoryOperation, iamDirectoryFailure }
|
|
|
4
4
|
export { iamModule } from './iam.js';
|
|
5
5
|
export { GenesisError, errorCatalog, errorBody, errorToken, errorOutcome } from './errors.js';
|
|
6
6
|
export { ERROR_BODY_LIMITS, cardNumber, redactErrorBody, redactErrorText, secretField } from './redact.js';
|
|
7
|
-
export { requireSecureUrl } from './urls.js';
|
|
7
|
+
export { requireSecureUrl, redactUserinfo, stripUserinfo } from './urls.js';
|
|
8
8
|
export { operationTable, services, type OperationId } from './generated/operations.js';
|
|
9
9
|
export type { OperationInput, OperationOutput, OperationMeta, ServiceMeta } from './types.js';
|
|
10
10
|
export type { paths, operations, components } from './generated/schema.js';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { iamDirectoryOperations, isIamDirectoryOperation, iamDirectoryFailure }
|
|
|
4
4
|
export { iamModule } from './iam.js';
|
|
5
5
|
export { GenesisError, errorCatalog, errorBody, errorToken, errorOutcome } from './errors.js';
|
|
6
6
|
export { ERROR_BODY_LIMITS, cardNumber, redactErrorBody, redactErrorText, secretField } from './redact.js';
|
|
7
|
-
export { requireSecureUrl } from './urls.js';
|
|
7
|
+
export { requireSecureUrl, redactUserinfo, stripUserinfo } from './urls.js';
|
|
8
8
|
export { operationTable, services } from './generated/operations.js';
|
|
9
9
|
export { hivesUploadBody, MAX_WASM_BYTES } from './hives-upload.js';
|
|
10
10
|
export { validateHivesInvocation, hivesFunctionResponseHeaders, HivesOperationError, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES } from './hives-functions.js';
|
package/dist/redact.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Redaction for service error bodies and messages before the CLI or MCP prints them. The design is
|
|
3
|
-
* conservative: when a key or value might be a secret, it is replaced with `[redacted]`. Field names are
|
|
4
|
-
* matched after normalisation (camelCase split, `-`/`.`/space to `_`, lower-cased), so `x-api-key`,
|
|
5
|
-
* `privateKey`, `Set-Cookie` and `client_secret` are all the same kind of name.
|
|
6
|
-
*/
|
|
7
1
|
export declare const REDACTED = "[redacted]";
|
|
8
2
|
/** Bounds of the printed body: serialized bytes, per string, per array/object, nesting and the oversize preview. */
|
|
9
3
|
export declare const ERROR_BODY_LIMITS: {
|
package/dist/redact.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* matched after normalisation (camelCase split, `-`/`.`/space to `_`, lower-cased), so `x-api-key`,
|
|
5
5
|
* `privateKey`, `Set-Cookie` and `client_secret` are all the same kind of name.
|
|
6
6
|
*/
|
|
7
|
+
import { redactUserinfo } from './urls.js';
|
|
7
8
|
export const REDACTED = '[redacted]';
|
|
8
9
|
/** Bounds of the printed body: serialized bytes, per string, per array/object, nesting and the oversize preview. */
|
|
9
10
|
export const ERROR_BODY_LIMITS = { bytes: 16384, string: 2048, items: 100, depth: 8, preview: 4096 };
|
|
@@ -174,8 +175,8 @@ const SECRET_TEXT = [
|
|
|
174
175
|
[/(?<![A-Za-z0-9])ek_(?:live|test)_[A-Za-z0-9_-]{8,}/g, REDACTED],
|
|
175
176
|
// PEM private keys (whole block, or to the end when cut).
|
|
176
177
|
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, REDACTED],
|
|
177
|
-
// URL userinfo: scheme://user:pass@host.
|
|
178
|
-
[
|
|
178
|
+
// URL userinfo: scheme://user:pass@host, and the scheme-less, protocol-relative, percent-encoded and JSON-escaped forms.
|
|
179
|
+
[/^[\s\S]*$/, (t) => redactUserinfo(t, REDACTED)],
|
|
179
180
|
// An Authorization / Proxy-Authorization header in text, any scheme.
|
|
180
181
|
[/\b((?:proxy[-_ ]?)?authorization["']?\s*[:=]\s*["']?)(?!\[redacted\])(?:[A-Za-z][A-Za-z0-9_-]*\s+)?[^\s"',;]{8,}/gi, `$1${REDACTED}`],
|
|
181
182
|
// Auth schemes followed by a credential.
|
package/dist/urls.d.ts
CHANGED
|
@@ -4,3 +4,12 @@
|
|
|
4
4
|
* Credentials ride on every request, so a plain-http endpoint off the local machine is refused.
|
|
5
5
|
*/
|
|
6
6
|
export declare function requireSecureUrl(value: string, what: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Replace the userinfo of every URL in `text` with `mask` (default `***`): `https://user:pass@host` becomes
|
|
9
|
+
* `https://***@host` and `user:pass@host` becomes `***@host`, including scheme-less, protocol-relative,
|
|
10
|
+
* percent-encoded (also double-encoded) and JSON-escaped forms, and passwords holding an unencoded `@`.
|
|
11
|
+
* Linear time on any input. Use on error and status text only, never on data the user asked to see.
|
|
12
|
+
*/
|
|
13
|
+
export declare function redactUserinfo(text: string, mask?: string): string;
|
|
14
|
+
/** `url` without its userinfo (`https://u:p@host/x` becomes `https://host/x`); text that is not an absolute URL passes through redactUserinfo. */
|
|
15
|
+
export declare function stripUserinfo(url: string): string;
|
package/dist/urls.js
CHANGED
|
@@ -29,3 +29,64 @@ export function requireSecureUrl(value, what) {
|
|
|
29
29
|
return value;
|
|
30
30
|
throw new Error(`${what} must use https (http is allowed only on loopback hosts)`);
|
|
31
31
|
}
|
|
32
|
+
// The scheme before the slashes is left as it is, so it is never matched. Every quantifier is bounded (userinfo 512, bare user 64 / password 128) and none is nested or overlaps
|
|
33
|
+
// another, so one start position costs O(1). On top of that, only the stretches within WINDOW characters before an
|
|
34
|
+
// `@` (or `%40`) are scanned at all: a userinfo cannot end anywhere else. Redaction is therefore linear in the text
|
|
35
|
+
// length times a constant, and text with no `@`, fullwidth `@` or `%40` is returned as it is.
|
|
36
|
+
// scheme://userinfo@, //userinfo@, https:/\userinfo@ and JSON-escaped :\/\/userinfo@ (greedy to the last @ before the path).
|
|
37
|
+
const USERINFO = /([/\\]{2}|\\\/\\\/)[^\s/?#\\"'<>]{0,512}[@@]/gi;
|
|
38
|
+
// The same, percent-encoded (also double-encoded): https%3A%2F%2Fuser%3Apass%40host, https%253A%252F%252Fuser%2540host.
|
|
39
|
+
const ENCODED_USERINFO = /(%(?:25){0,2}2F%(?:25){0,2}2F)(?:(?!%(?:25){0,2}2F)[^\s/?#&"'<>]){0,512}%(?:25){0,2}40/gi;
|
|
40
|
+
// Scheme-less user:pass@host (a colon is required, so plain addresses like user@host are left alone).
|
|
41
|
+
const BARE_USERINFO = /(^|[\s"'(<=,;[{])[^\s/?#@@:"'<>\\]{1,64}:[^\s/?#"'<>\\]{0,128}[@@](?=[\w[])/g;
|
|
42
|
+
/** Every place a userinfo can end: `@`, fullwidth `@`, or an encoded `%40` / `%2540`. */
|
|
43
|
+
const AT_HITS = /[@@]|%(?:25){0,2}40/gi;
|
|
44
|
+
/** The longest URL-form match (slashes and userinfo, encoded) is under 640 characters; the bare form is under 200. */
|
|
45
|
+
const URL_WINDOW = 700;
|
|
46
|
+
const BARE_WINDOW = 200;
|
|
47
|
+
/** Apply `fn` to the stretches of `text` within `window` characters before an `@`-like hit, and copy the rest as it is. */
|
|
48
|
+
function overHits(text, window, fn) {
|
|
49
|
+
AT_HITS.lastIndex = 0;
|
|
50
|
+
let hit = AT_HITS.exec(text);
|
|
51
|
+
if (!hit)
|
|
52
|
+
return text;
|
|
53
|
+
let out = '';
|
|
54
|
+
let copied = 0;
|
|
55
|
+
let start = Math.max(0, hit.index - window);
|
|
56
|
+
let end = hit.index + hit[0].length + 1;
|
|
57
|
+
const flush = () => {
|
|
58
|
+
// A sentinel before a window that does not start the text keeps `^` (start of text) from matching mid-text.
|
|
59
|
+
const piece = start > 0 ? fn(`x${text.slice(start, end)}`).slice(1) : fn(text.slice(start, end));
|
|
60
|
+
out += text.slice(copied, start) + piece;
|
|
61
|
+
copied = Math.min(end, text.length);
|
|
62
|
+
};
|
|
63
|
+
for (hit = AT_HITS.exec(text); hit; hit = AT_HITS.exec(text)) {
|
|
64
|
+
const s = Math.max(0, hit.index - window);
|
|
65
|
+
if (s <= end) {
|
|
66
|
+
end = hit.index + hit[0].length + 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
flush();
|
|
70
|
+
start = s;
|
|
71
|
+
end = hit.index + hit[0].length + 1;
|
|
72
|
+
}
|
|
73
|
+
flush();
|
|
74
|
+
return out + text.slice(copied);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Replace the userinfo of every URL in `text` with `mask` (default `***`): `https://user:pass@host` becomes
|
|
78
|
+
* `https://***@host` and `user:pass@host` becomes `***@host`, including scheme-less, protocol-relative,
|
|
79
|
+
* percent-encoded (also double-encoded) and JSON-escaped forms, and passwords holding an unencoded `@`.
|
|
80
|
+
* Linear time on any input. Use on error and status text only, never on data the user asked to see.
|
|
81
|
+
*/
|
|
82
|
+
export function redactUserinfo(text, mask = '***') {
|
|
83
|
+
// Pre-check: no `@`, `@` or `%40` means no userinfo in any form.
|
|
84
|
+
if (!/[@@]|%(?:25){0,2}40/i.test(text))
|
|
85
|
+
return text;
|
|
86
|
+
const urlForms = overHits(text, URL_WINDOW, (t) => t.replace(ENCODED_USERINFO, `$1${mask}%40`).replace(USERINFO, `$1${mask}@`));
|
|
87
|
+
return overHits(urlForms, BARE_WINDOW, (t) => t.replace(BARE_USERINFO, `$1${mask}@`));
|
|
88
|
+
}
|
|
89
|
+
/** `url` without its userinfo (`https://u:p@host/x` becomes `https://host/x`); text that is not an absolute URL passes through redactUserinfo. */
|
|
90
|
+
export function stripUserinfo(url) {
|
|
91
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/?#]*@/i, '$1') : redactUserinfo(url);
|
|
92
|
+
}
|