@runuai/host 0.9.170 → 0.9.172
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/lib/agents/claude.ts +13 -0
- package/lib/agents/codex.ts +11 -5
- package/lib/agents/session-state.ts +77 -0
- package/lib/agents/transport.ts +2 -0
- package/lib/agents/types.ts +3 -0
- package/lib/agents/uai.ts +3 -25
- package/lib/assignment-evidence.ts +2 -1
- package/lib/capture-flow.ts +14 -1
- package/lib/cloud-credential-leases.ts +9 -3
- package/lib/cloud-credential-runtime.ts +8 -0
- package/lib/connection-secret-redaction.ts +25 -0
- package/lib/credential-cleanup-inventory.ts +2 -1
- package/lib/credential-consumers/engines.ts +29 -5
- package/lib/credentials-activation-authority.ts +158 -10
- package/lib/custom-connection-capture.ts +395 -0
- package/lib/custom-connection-launch.ts +299 -0
- package/lib/custom-connection-runtime.ts +70 -0
- package/lib/engine-accounts.ts +1 -1
- package/lib/engine-credential-env.ts +2 -0
- package/lib/internal-connections.ts +5 -0
- package/lib/legacy-runtime-retirement.ts +43 -8
- package/lib/obs.ts +2 -1
- package/lib/openrouter-budget.ts +247 -0
- package/lib/orchestrator.ts +308 -26
- package/lib/secrets-proxy/address.ts +23 -0
- package/lib/task-credential-cleanup.ts +13 -3
- package/package.json +1 -1
- package/runner/runner.mjs +231 -3
- package/src/capture-wire.ts +102 -0
- package/src/cloud-credentials.ts +36 -372
- package/src/credential-location.ts +248 -0
- package/src/credential-wire-validation.ts +25 -0
- package/src/custom-connections.ts +207 -0
- package/src/main.ts +116 -3
- package/src/protocol.ts +111 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/** Pure credential path grammar shared by capture and lease validation. */
|
|
2
|
+
/** What a task may receive: a static key, or an OAuth login's brokered copy. */
|
|
3
|
+
export const LEASE_PARAMETER_ROOT = "/uai/lease";
|
|
4
|
+
/**
|
|
5
|
+
* The full OAuth material only the broker reads. Outside `/uai` altogether:
|
|
6
|
+
* `GetParametersByPath` returns every descendant of a path its caller may
|
|
7
|
+
* read, even one a Deny names, so containment comes from the root alone.
|
|
8
|
+
*/
|
|
9
|
+
export const BROKER_PARAMETER_ROOT = "/uai-broker";
|
|
10
|
+
|
|
11
|
+
export type CredentialClass = "engine" | "mcp" | "github" | "ssh";
|
|
12
|
+
export type CredentialScope = "org" | "user";
|
|
13
|
+
|
|
14
|
+
/** Where one revision of one credential lives. */
|
|
15
|
+
export interface CredentialLocation {
|
|
16
|
+
class: CredentialClass;
|
|
17
|
+
/** `github` and `ssh` are always `user`: one per person. */
|
|
18
|
+
scope: CredentialScope;
|
|
19
|
+
/** The org id for `org` scope, the user id for `user` scope. */
|
|
20
|
+
ownerId: string;
|
|
21
|
+
/** `engine` only: the engine kind (`codex`, `claude`, …). */
|
|
22
|
+
engineKind?: string;
|
|
23
|
+
/** `engine` and `mcp` only: the credential's stable id. */
|
|
24
|
+
credentialId?: string;
|
|
25
|
+
/** One immutable revision of the material. */
|
|
26
|
+
revision: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** SSM allows `[A-Za-z0-9_.-]` in a path segment. An id that already fits,
|
|
30
|
+
* starts with a letter or digit and has no dot is used as it is; any other
|
|
31
|
+
* id is `-` followed by the hex of its UTF-8 bytes. No plain id starts with
|
|
32
|
+
* `-`, so the mapping is injective. Legacy workspace ids
|
|
33
|
+
* (`personal:<user>`) take the second form. */
|
|
34
|
+
const PLAIN_ID_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
35
|
+
const ENCODED_ID_SEGMENT = /^-(?:[0-9a-f]{2})+$/;
|
|
36
|
+
const ENGINE_KIND = /^[a-z][a-z0-9-]{0,31}$/;
|
|
37
|
+
export const MAX_CREDENTIAL_ID_CHARS = 128;
|
|
38
|
+
/** SSM's limit for a parameter name, its ARN included. */
|
|
39
|
+
export const MAX_PARAMETER_NAME_CHARS = 1011;
|
|
40
|
+
|
|
41
|
+
export function isIdValue(value: unknown): value is string {
|
|
42
|
+
return (
|
|
43
|
+
typeof value === "string" &&
|
|
44
|
+
value.length > 0 &&
|
|
45
|
+
value.length <= MAX_CREDENTIAL_ID_CHARS &&
|
|
46
|
+
!/[\u0000-\u001f\u007f]/.test(value)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function utf8Hex(value: string): string {
|
|
51
|
+
let out = "";
|
|
52
|
+
for (const byte of new TextEncoder().encode(value)) {
|
|
53
|
+
out += byte.toString(16).padStart(2, "0");
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function fromUtf8Hex(hex: string): string | null {
|
|
59
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
60
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
61
|
+
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The path segment for an id (see `PLAIN_ID_SEGMENT`). Throws on an id no
|
|
71
|
+
* segment may carry: empty, too long, or containing control characters. */
|
|
72
|
+
export function idPathSegment(id: string): string {
|
|
73
|
+
if (!isIdValue(id)) throw new Error("invalid credential path id");
|
|
74
|
+
return PLAIN_ID_SEGMENT.test(id) ? id : `-${utf8Hex(id)}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Inverse of `idPathSegment`; null for a segment it never produces. */
|
|
78
|
+
export function parseIdPathSegment(segment: string): string | null {
|
|
79
|
+
if (PLAIN_ID_SEGMENT.test(segment)) {
|
|
80
|
+
return segment.length <= MAX_CREDENTIAL_ID_CHARS ? segment : null;
|
|
81
|
+
}
|
|
82
|
+
if (!ENCODED_ID_SEGMENT.test(segment)) return null;
|
|
83
|
+
const id = fromUtf8Hex(segment.slice(1));
|
|
84
|
+
if (id === null || !isIdValue(id) || PLAIN_ID_SEGMENT.test(id)) return null;
|
|
85
|
+
return id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function isEngineKind(value: unknown): value is string {
|
|
89
|
+
return typeof value === "string" && ENGINE_KIND.test(value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function locationSegments(location: CredentialLocation): string[] {
|
|
93
|
+
const owner = location.scope === "org" ? "orgs" : "users";
|
|
94
|
+
const segments = [owner, idPathSegment(location.ownerId)];
|
|
95
|
+
switch (location.class) {
|
|
96
|
+
case "engine": {
|
|
97
|
+
if (!isEngineKind(location.engineKind)) {
|
|
98
|
+
throw new Error("invalid engine kind");
|
|
99
|
+
}
|
|
100
|
+
if (location.credentialId === undefined) {
|
|
101
|
+
throw new Error("an engine credential needs its id");
|
|
102
|
+
}
|
|
103
|
+
segments.push(
|
|
104
|
+
"engines",
|
|
105
|
+
location.engineKind,
|
|
106
|
+
idPathSegment(location.credentialId),
|
|
107
|
+
);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "mcp": {
|
|
111
|
+
if (location.engineKind !== undefined) {
|
|
112
|
+
throw new Error("an MCP credential has no engine kind");
|
|
113
|
+
}
|
|
114
|
+
if (location.credentialId === undefined) {
|
|
115
|
+
throw new Error("an MCP credential needs its id");
|
|
116
|
+
}
|
|
117
|
+
segments.push("mcp", idPathSegment(location.credentialId));
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "github":
|
|
121
|
+
case "ssh": {
|
|
122
|
+
if (location.scope !== "user") {
|
|
123
|
+
throw new Error(`${location.class} credentials belong to a user`);
|
|
124
|
+
}
|
|
125
|
+
if (
|
|
126
|
+
location.engineKind !== undefined ||
|
|
127
|
+
location.credentialId !== undefined
|
|
128
|
+
) {
|
|
129
|
+
throw new Error(`${location.class} has one credential per person`);
|
|
130
|
+
}
|
|
131
|
+
segments.push(location.class);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
throw new Error("invalid credential class");
|
|
136
|
+
}
|
|
137
|
+
segments.push(idPathSegment(location.revision));
|
|
138
|
+
return segments;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function nameUnder(root: string, location: CredentialLocation): string {
|
|
142
|
+
const name = `${root}/${locationSegments(location).join("/")}`;
|
|
143
|
+
if (name.length > MAX_PARAMETER_NAME_CHARS) {
|
|
144
|
+
throw new Error("credential parameter name too long");
|
|
145
|
+
}
|
|
146
|
+
return name;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The lease-readable parameter of one revision. */
|
|
150
|
+
export function leaseParameterName(location: CredentialLocation): string {
|
|
151
|
+
return nameUnder(LEASE_PARAMETER_ROOT, location);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The broker-only parameter of one revision of an OAuth credential. GitHub
|
|
155
|
+
* tokens and SSH keys are static and have none. */
|
|
156
|
+
export function brokerParameterName(location: CredentialLocation): string {
|
|
157
|
+
if (location.class === "github" || location.class === "ssh") {
|
|
158
|
+
throw new Error(`${location.class} credentials have no broker part`);
|
|
159
|
+
}
|
|
160
|
+
return nameUnder(BROKER_PARAMETER_ROOT, location);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function parseUnder(root: string, name: string): CredentialLocation | null {
|
|
164
|
+
if (
|
|
165
|
+
typeof name !== "string" ||
|
|
166
|
+
name.length > MAX_PARAMETER_NAME_CHARS ||
|
|
167
|
+
!name.startsWith(`${root}/`)
|
|
168
|
+
) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const parts = name.slice(root.length + 1).split("/");
|
|
172
|
+
const [ownerKind, ownerSegment] = parts;
|
|
173
|
+
const scope: CredentialScope | null =
|
|
174
|
+
ownerKind === "orgs" ? "org" : ownerKind === "users" ? "user" : null;
|
|
175
|
+
if (scope === null || ownerSegment === undefined) return null;
|
|
176
|
+
const ownerId = parseIdPathSegment(ownerSegment);
|
|
177
|
+
if (ownerId === null) return null;
|
|
178
|
+
const revisionOf = (segment: string | undefined): string | null =>
|
|
179
|
+
segment === undefined ? null : parseIdPathSegment(segment);
|
|
180
|
+
|
|
181
|
+
if (parts[2] === "engines" && parts.length === 6) {
|
|
182
|
+
const credentialId = parseIdPathSegment(parts[4]!);
|
|
183
|
+
const revision = revisionOf(parts[5]);
|
|
184
|
+
if (!isEngineKind(parts[3]) || credentialId === null || revision === null) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
class: "engine",
|
|
189
|
+
scope,
|
|
190
|
+
ownerId,
|
|
191
|
+
engineKind: parts[3],
|
|
192
|
+
credentialId,
|
|
193
|
+
revision,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (parts[2] === "mcp" && parts.length === 5) {
|
|
197
|
+
const credentialId = parseIdPathSegment(parts[3]!);
|
|
198
|
+
const revision = revisionOf(parts[4]);
|
|
199
|
+
if (credentialId === null || revision === null) return null;
|
|
200
|
+
return { class: "mcp", scope, ownerId, credentialId, revision };
|
|
201
|
+
}
|
|
202
|
+
if (
|
|
203
|
+
(parts[2] === "github" || parts[2] === "ssh") &&
|
|
204
|
+
parts.length === 4 &&
|
|
205
|
+
scope === "user"
|
|
206
|
+
) {
|
|
207
|
+
const revision = revisionOf(parts[3]);
|
|
208
|
+
if (revision === null) return null;
|
|
209
|
+
return { class: parts[2], scope, ownerId, revision };
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function parseLeaseParameterName(
|
|
215
|
+
name: string,
|
|
216
|
+
): CredentialLocation | null {
|
|
217
|
+
return parseUnder(LEASE_PARAMETER_ROOT, name);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function parseBrokerParameterName(
|
|
221
|
+
name: string,
|
|
222
|
+
): CredentialLocation | null {
|
|
223
|
+
const location = parseUnder(BROKER_PARAMETER_ROOT, name);
|
|
224
|
+
return location && (location.class === "engine" || location.class === "mcp")
|
|
225
|
+
? location
|
|
226
|
+
: null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function sameLocation(a: CredentialLocation, b: CredentialLocation): boolean {
|
|
230
|
+
return (
|
|
231
|
+
a.class === b.class &&
|
|
232
|
+
a.scope === b.scope &&
|
|
233
|
+
a.ownerId === b.ownerId &&
|
|
234
|
+
a.engineKind === b.engineKind &&
|
|
235
|
+
a.credentialId === b.credentialId &&
|
|
236
|
+
a.revision === b.revision
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const TAG_VALUE = /^[\p{L}\p{Z}\p{N}_.:/=+\-@]{1,256}$/u;
|
|
241
|
+
|
|
242
|
+
/** STS session tag and SSM resource tag values: the same grammar, refused
|
|
243
|
+
* rather than rewritten, so a principal tag and a resource tag written for
|
|
244
|
+
* one id always compare equal. */
|
|
245
|
+
export function isTagValue(value: unknown): value is string {
|
|
246
|
+
return typeof value === "string" && TAG_VALUE.test(value);
|
|
247
|
+
}
|
|
248
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Leaf structural wire validators. */
|
|
2
|
+
type WireObject = Record<string, unknown>;
|
|
3
|
+
export function exactObject(
|
|
4
|
+
value: unknown,
|
|
5
|
+
required: readonly string[],
|
|
6
|
+
optional: readonly string[] = [],
|
|
7
|
+
): WireObject | null {
|
|
8
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
9
|
+
const object = value as WireObject;
|
|
10
|
+
const keys = Object.keys(object);
|
|
11
|
+
if (
|
|
12
|
+
keys.some(key => !required.includes(key) && !optional.includes(key)) ||
|
|
13
|
+
required.some((key) => !Object.hasOwn(object, key))
|
|
14
|
+
) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return object;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isPositiveInteger(value: unknown): value is number {
|
|
21
|
+
return Number.isSafeInteger(value) && (value as number) > 0;
|
|
22
|
+
}
|
|
23
|
+
export function isEpochMs(value: unknown): value is number {
|
|
24
|
+
return isPositiveInteger(value);
|
|
25
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** ADR-164: shared, secret-blind metadata and strictly bounded sealed settings.
|
|
2
|
+
* This module is pure and is re-exported by cloud-credentials and protocol. */
|
|
3
|
+
import { parseCaptureFlowWire, parseCaptureResultWire, type CaptureFlowWire, type CaptureResultWire } from "./capture-wire";
|
|
4
|
+
|
|
5
|
+
export const CUSTOM_CONNECTIONS_FEATURE = "customConnections";
|
|
6
|
+
export const ENGINE_CONNECT_CUSTOM_FEATURE = "engineConnectCustom";
|
|
7
|
+
export const ENGINE_CONNECT_CUSTOM_MANUAL_FEATURE = "engineConnectCustomManual";
|
|
8
|
+
export const INTERNAL_CONNECTIONS_FEATURE = "internalConnections";
|
|
9
|
+
export const CUSTOM_CONNECTION_REASONS = ["unreachable", "private-address", "unauthorized", "forbidden", "model-not-found", "wrong-protocol", "rate-limited", "timeout", "upstream-error", "invalid-models", "expired", "unsupported"] as const;
|
|
10
|
+
export type CustomConnectionReason = typeof CUSTOM_CONNECTION_REASONS[number];
|
|
11
|
+
export type CustomConnectionEngine = "claude" | "codex";
|
|
12
|
+
export type CustomConnectionProvider = "openrouter" | "bedrock" | "custom";
|
|
13
|
+
export type CustomConnectionTransport = "runtime" | "mantle";
|
|
14
|
+
export type CustomConnectionAuthMode = "bearer" | "x-api-key";
|
|
15
|
+
export type AccountBinding = "required" | "preferred";
|
|
16
|
+
export const CUSTOM_CONNECTION_COMBINATIONS = ["openrouter:claude", "openrouter:codex", "bedrock:claude:runtime", "bedrock:codex:runtime", "bedrock:codex:mantle", "custom:claude", "custom:codex", "custom-internal:claude", "custom-internal:codex"] as const;
|
|
17
|
+
export type CustomConnectionCombination = typeof CUSTOM_CONNECTION_COMBINATIONS[number];
|
|
18
|
+
|
|
19
|
+
export interface CustomConnectionSettings {
|
|
20
|
+
providerType: CustomConnectionProvider;
|
|
21
|
+
/** Absent is public, for compatibility with existing revisions. */
|
|
22
|
+
internal?: boolean;
|
|
23
|
+
engine: CustomConnectionEngine;
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
region?: string;
|
|
26
|
+
transport?: CustomConnectionTransport;
|
|
27
|
+
expiresAt?: number | null;
|
|
28
|
+
authMode: CustomConnectionAuthMode;
|
|
29
|
+
}
|
|
30
|
+
export interface CustomConnectionMaterial extends CustomConnectionSettings {
|
|
31
|
+
kind: "custom-connection";
|
|
32
|
+
apiKey: string;
|
|
33
|
+
}
|
|
34
|
+
/** The manifest deliberately has a host, never a URL or secret. */
|
|
35
|
+
export interface CustomConnectionLease {
|
|
36
|
+
settingsHash: string;
|
|
37
|
+
providerType: CustomConnectionProvider;
|
|
38
|
+
/** Absent is public, for compatibility with existing revisions. */
|
|
39
|
+
internal?: boolean;
|
|
40
|
+
host: string;
|
|
41
|
+
region?: string;
|
|
42
|
+
transport?: CustomConnectionTransport;
|
|
43
|
+
expiresAt?: number | null;
|
|
44
|
+
models: string[];
|
|
45
|
+
defaultModel: string;
|
|
46
|
+
backgroundModel: string | null;
|
|
47
|
+
authMode: CustomConnectionAuthMode;
|
|
48
|
+
}
|
|
49
|
+
export interface OpenRouterBudget {
|
|
50
|
+
usage: number | null;
|
|
51
|
+
limit: number | null;
|
|
52
|
+
remaining: number | null;
|
|
53
|
+
effectiveBudget: number | null;
|
|
54
|
+
resetsAt: number | null;
|
|
55
|
+
keyExpiresAt: number | null;
|
|
56
|
+
}
|
|
57
|
+
export type OpenRouterBudgetResult = ({ status: "ok" } & OpenRouterBudget) | { status: "error"; reason: CustomConnectionReason };
|
|
58
|
+
export type OpenRouterBudgetFrame = {
|
|
59
|
+
kind: "openrouter.budget"; taskId: string; credentialId: string; revision: string; observedAt: number;
|
|
60
|
+
} & OpenRouterBudgetResult;
|
|
61
|
+
|
|
62
|
+
export interface EngineConnectCustomDiscoverFrame {
|
|
63
|
+
kind: "engine.connect.custom.discover"; opId: string;
|
|
64
|
+
settings: CustomConnectionMaterial;
|
|
65
|
+
enabled: CustomConnectionCombination[];
|
|
66
|
+
}
|
|
67
|
+
export type CustomConnectionDiscoverResult = { ok: true; models: string[]; truncated?: boolean } | { ok: false; reason: CustomConnectionReason };
|
|
68
|
+
export interface EngineConnectCustomDiscoveredFrame {
|
|
69
|
+
kind: "engine.connect.custom.discovered"; opId: string; result: CustomConnectionDiscoverResult;
|
|
70
|
+
}
|
|
71
|
+
export interface EngineConnectCustomSaveFrame {
|
|
72
|
+
kind: "engine.connect.custom.save"; opId: string;
|
|
73
|
+
settings: CustomConnectionMaterial;
|
|
74
|
+
enabled: CustomConnectionCombination[];
|
|
75
|
+
models: string[]; defaultModel: string; backgroundModel: string | null;
|
|
76
|
+
flow: CaptureFlowWire;
|
|
77
|
+
}
|
|
78
|
+
/** Internal connections have a distinct manual-only operation. */
|
|
79
|
+
export interface EngineConnectCustomManualSaveFrame extends Omit<EngineConnectCustomSaveFrame, "kind"> {
|
|
80
|
+
kind: "engine.connect.custom.manual-save";
|
|
81
|
+
}
|
|
82
|
+
export type CustomConnectionSaveResult = { ok: true; capture: CaptureResultWire; authMode: CustomConnectionAuthMode; budget?: OpenRouterBudgetResult } | { ok: false; reason: CustomConnectionReason };
|
|
83
|
+
export interface EngineConnectCustomSavedFrame {
|
|
84
|
+
kind: "engine.connect.custom.saved"; opId: string; result: CustomConnectionSaveResult;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function connectionObject(value: unknown, required: readonly string[], optional: readonly string[] = []): Record<string, unknown> | null {
|
|
88
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
89
|
+
const o = value as Record<string, unknown>;
|
|
90
|
+
return required.every(k => Object.hasOwn(o, k)) && Object.keys(o).every(k => required.includes(k) || optional.includes(k)) ? o : null;
|
|
91
|
+
}
|
|
92
|
+
export function isCustomConnectionReason(v: unknown): v is CustomConnectionReason {
|
|
93
|
+
return typeof v === "string" && (CUSTOM_CONNECTION_REASONS as readonly string[]).includes(v);
|
|
94
|
+
}
|
|
95
|
+
export function parseCustomConnectionEnabled(v: unknown): CustomConnectionCombination[] | null {
|
|
96
|
+
return Array.isArray(v) && v.length <= CUSTOM_CONNECTION_COMBINATIONS.length && new Set(v).size === v.length && v.every(x => (CUSTOM_CONNECTION_COMBINATIONS as readonly unknown[]).includes(x)) ? [...v] : null;
|
|
97
|
+
}
|
|
98
|
+
export function customConnectionCombination(s: Pick<CustomConnectionSettings, "providerType" | "engine" | "transport" | "internal">): CustomConnectionCombination {
|
|
99
|
+
return `${s.providerType === "custom" && s.internal === true ? "custom-internal" : s.providerType}:${s.engine}${s.providerType === "bedrock" ? `:${s.transport}` : ""}` as CustomConnectionCombination;
|
|
100
|
+
}
|
|
101
|
+
export function customConnectionEnabled(s: Pick<CustomConnectionSettings, "providerType" | "engine" | "transport" | "internal">, enabled: readonly CustomConnectionCombination[]): boolean {
|
|
102
|
+
return enabled.includes(customConnectionCombination(s));
|
|
103
|
+
}
|
|
104
|
+
export function canonicalConnectionEndpoint(s: Pick<CustomConnectionSettings, "providerType" | "engine" | "region" | "transport">): string | null {
|
|
105
|
+
if (s.engine !== "claude" && s.engine !== "codex") return null;
|
|
106
|
+
if (s.providerType === "openrouter") return s.engine === "claude" ? "https://openrouter.ai/api" : "https://openrouter.ai/api/v1";
|
|
107
|
+
if (s.providerType !== "bedrock" || !s.region || !/^(?:[a-z]{2})(?:-[a-z]+){1,3}-[1-9][0-9]?$/.test(s.region)) return null;
|
|
108
|
+
if (s.transport === "runtime") return `https://bedrock-runtime.${s.region}.amazonaws.com${s.engine === "codex" ? "/openai/v1" : ""}`;
|
|
109
|
+
return s.transport === "mantle" && s.engine === "codex" ? `https://bedrock-mantle.${s.region}.api.aws/openai/v1` : null;
|
|
110
|
+
}
|
|
111
|
+
export function isCustomConnectionUrl(v: unknown, internal = false): v is string {
|
|
112
|
+
if (typeof v !== "string" || !v || v.length > 2048 || /[\s\u0000-\u001f\u007f]/.test(v) || v.endsWith("/")) return false;
|
|
113
|
+
try {
|
|
114
|
+
const u = new URL(v);
|
|
115
|
+
const hostname = u.hostname.replace(/^\[|\]$/g, "");
|
|
116
|
+
const dnsName = hostname.length <= 253 && hostname.replace(/\.$/, "").split(".").every(label => /^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(label));
|
|
117
|
+
return (u.protocol === "https:" || (internal && u.protocol === "http:")) && !u.username && !u.password && !u.search && !u.hash && ((u.hostname.startsWith("[") && u.hostname.endsWith("]")) || dnsName);
|
|
118
|
+
} catch { return false; }
|
|
119
|
+
}
|
|
120
|
+
export function parseCustomConnectionSettings(value: unknown): CustomConnectionSettings | null {
|
|
121
|
+
const o = connectionObject(value, ["providerType", "engine", "baseUrl", "authMode"], ["region", "transport", "expiresAt", "internal"]);
|
|
122
|
+
if (!o || !["openrouter", "bedrock", "custom"].includes(o.providerType as string) || !["claude", "codex"].includes(o.engine as string) || !isCustomConnectionUrl(o.baseUrl, o.providerType === "custom" && o.internal === true) || !["bearer", "x-api-key"].includes(o.authMode as string)) return null;
|
|
123
|
+
if (o.internal !== undefined && (typeof o.internal !== "boolean" || (o.internal && o.providerType !== "custom"))) return null;
|
|
124
|
+
const s = o as unknown as CustomConnectionSettings;
|
|
125
|
+
if (s.authMode !== "bearer" && !(s.providerType === "custom" && s.engine === "claude")) return null;
|
|
126
|
+
if (s.providerType === "bedrock") {
|
|
127
|
+
if (s.expiresAt !== null && s.expiresAt !== undefined && (!Number.isSafeInteger(s.expiresAt) || s.expiresAt <= 0)) return null;
|
|
128
|
+
if (s.baseUrl !== canonicalConnectionEndpoint(s)) return null;
|
|
129
|
+
} else if (o.region !== undefined || o.transport !== undefined || o.expiresAt !== undefined || (s.providerType === "openrouter" && s.baseUrl !== canonicalConnectionEndpoint(s))) return null;
|
|
130
|
+
return { ...s, ...(s.providerType === "custom" ? { baseUrl: new URL(s.baseUrl).href.replace(/\/$/, "") } : {}) };
|
|
131
|
+
}
|
|
132
|
+
export function parseCustomConnectionMaterial(value: unknown): CustomConnectionMaterial | null {
|
|
133
|
+
const o = connectionObject(value, ["kind", "providerType", "engine", "baseUrl", "apiKey", "authMode"], ["region", "transport", "expiresAt", "internal"]);
|
|
134
|
+
if (!o || o.kind !== "custom-connection" || typeof o.apiKey !== "string" || !o.apiKey || /\s|[\u0000-\u001f\u007f]/.test(o.apiKey) || new TextEncoder().encode(o.apiKey).length > 8192) return null;
|
|
135
|
+
const settings = Object.fromEntries(Object.entries(o).filter(([key]) => key !== "kind" && key !== "apiKey"));
|
|
136
|
+
const parsed = parseCustomConnectionSettings(settings);
|
|
137
|
+
return parsed ? { kind: "custom-connection", ...parsed, apiKey: o.apiKey } : null;
|
|
138
|
+
}
|
|
139
|
+
export function validConnectionModels(models: unknown, defaultModel: unknown, backgroundModel: unknown, engine: CustomConnectionEngine, key = ""): models is string[] {
|
|
140
|
+
return Array.isArray(models) && models.length >= 1 && models.length <= 200 && new Set(models).size === models.length && models.every(x => typeof x === "string" && x.length > 0 && x.length <= 200 && !/[\s\u0000-\u001f\u007f]/.test(x) && (!key || !x.includes(key))) && typeof defaultModel === "string" && models.includes(defaultModel) && (engine === "codex" ? backgroundModel === null : typeof backgroundModel === "string" && models.includes(backgroundModel));
|
|
141
|
+
}
|
|
142
|
+
export function parseCustomConnectionLease(value: unknown, engine: string): CustomConnectionLease | null {
|
|
143
|
+
const o = connectionObject(value, ["settingsHash", "providerType", "host", "models", "defaultModel", "backgroundModel", "authMode"], ["region", "transport", "expiresAt", "internal"]);
|
|
144
|
+
if (!o || typeof o.settingsHash !== "string" || !/^[a-f0-9]{64}$/.test(o.settingsHash) || typeof o.host !== "string" || !o.host || o.host.length > 253 || !validConnectionModels(o.models, o.defaultModel, o.backgroundModel, engine as CustomConnectionEngine)) return null;
|
|
145
|
+
const endpoint = canonicalConnectionEndpoint({ ...o, engine } as unknown as CustomConnectionSettings);
|
|
146
|
+
const settings = parseCustomConnectionSettings({ providerType: o.providerType, engine, baseUrl: endpoint ?? `https://${o.host}`, authMode: o.authMode, ...(o.internal !== undefined ? { internal: o.internal } : {}), ...(o.region !== undefined ? { region: o.region } : {}), ...(o.transport !== undefined ? { transport: o.transport } : {}), ...(o.expiresAt !== undefined ? { expiresAt: o.expiresAt } : {}) });
|
|
147
|
+
if (!settings || new URL(settings.baseUrl).host !== o.host) return null;
|
|
148
|
+
return { ...o } as unknown as CustomConnectionLease;
|
|
149
|
+
}
|
|
150
|
+
export function parseOpenRouterBudgetResult(value: unknown): OpenRouterBudgetResult | null {
|
|
151
|
+
const base = connectionObject(value, ["status", "reason"]);
|
|
152
|
+
if (base?.status === "error" && isCustomConnectionReason(base.reason)) return { status: "error", reason: base.reason };
|
|
153
|
+
const o = connectionObject(value, ["status", "usage", "limit", "remaining", "effectiveBudget", "resetsAt", "keyExpiresAt"]);
|
|
154
|
+
if (!o || o.status !== "ok" || !["usage", "limit", "remaining", "effectiveBudget"].every(k => o[k] === null || (typeof o[k] === "number" && Number.isFinite(o[k]) && (o[k] as number) >= 0)) || !["resetsAt", "keyExpiresAt"].every(k => o[k] === null || (Number.isSafeInteger(o[k]) && (o[k] as number) > 0))) return null;
|
|
155
|
+
return { ...o } as unknown as OpenRouterBudgetResult;
|
|
156
|
+
}
|
|
157
|
+
export function parseOpenRouterBudgetFrame(value: unknown): OpenRouterBudgetFrame | null {
|
|
158
|
+
const o = connectionObject(value, ["kind", "taskId", "credentialId", "revision", "observedAt", "status"], ["usage", "limit", "remaining", "effectiveBudget", "resetsAt", "keyExpiresAt", "reason"]);
|
|
159
|
+
if (!o || o.kind !== "openrouter.budget" || ![o.taskId, o.credentialId, o.revision].every(v => typeof v === "string" && v.length > 0 && v.length <= 128 && !/[\u0000-\u001f\u007f]/.test(v)) || !Number.isSafeInteger(o.observedAt) || (o.observedAt as number) <= 0) return null;
|
|
160
|
+
const { kind, taskId, credentialId, revision, observedAt, ...result } = o;
|
|
161
|
+
const parsed = parseOpenRouterBudgetResult(result);
|
|
162
|
+
return parsed ? { kind, taskId, credentialId, revision, observedAt, ...parsed } as OpenRouterBudgetFrame : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function connectionOpId(v: unknown): v is string {
|
|
166
|
+
return typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(v);
|
|
167
|
+
}
|
|
168
|
+
export function parseEngineConnectCustomDiscoverFrame(value: unknown): EngineConnectCustomDiscoverFrame | null {
|
|
169
|
+
const o = connectionObject(value, ["kind", "opId", "settings", "enabled"]);
|
|
170
|
+
const settings = o ? parseCustomConnectionMaterial(o.settings) : null;
|
|
171
|
+
const enabled = o ? parseCustomConnectionEnabled(o.enabled) : null;
|
|
172
|
+
return o?.kind === "engine.connect.custom.discover" && connectionOpId(o.opId) && settings && enabled ? { kind: o.kind, opId: o.opId, settings, enabled } : null;
|
|
173
|
+
}
|
|
174
|
+
export function parseEngineConnectCustomSaveFrame(value: unknown): EngineConnectCustomSaveFrame | null {
|
|
175
|
+
const o = connectionObject(value, ["kind", "opId", "settings", "enabled", "models", "defaultModel", "backgroundModel", "flow"]);
|
|
176
|
+
const settings = o ? parseCustomConnectionMaterial(o.settings) : null;
|
|
177
|
+
const enabled = o ? parseCustomConnectionEnabled(o.enabled) : null;
|
|
178
|
+
const flow = o ? parseCaptureFlowWire(o.flow) : null;
|
|
179
|
+
if (!o || o.kind !== "engine.connect.custom.save" || !connectionOpId(o.opId) || !settings || !enabled || !flow || flow.location.class !== "engine" || flow.location.engineKind !== settings.engine || flow.brokerParameter !== null || !validConnectionModels(o.models, o.defaultModel, o.backgroundModel, settings.engine)) return null;
|
|
180
|
+
const wire: CaptureFlowWire = { credentialId: flow.credentialId, revision: flow.revision, leaseParameter: flow.leaseParameter, brokerParameter: flow.brokerParameter, orgId: flow.orgId, userId: flow.userId };
|
|
181
|
+
return { kind: o.kind, opId: o.opId, settings, enabled, models: [...o.models], defaultModel: o.defaultModel as string, backgroundModel: o.backgroundModel as string | null, flow: wire };
|
|
182
|
+
}
|
|
183
|
+
export function parseEngineConnectCustomManualSaveFrame(value: unknown): EngineConnectCustomManualSaveFrame | null {
|
|
184
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || (value as Record<string, unknown>).kind !== "engine.connect.custom.manual-save") return null;
|
|
185
|
+
const parsed = parseEngineConnectCustomSaveFrame({ ...value, kind: "engine.connect.custom.save" });
|
|
186
|
+
return parsed?.settings.providerType === "custom" && parsed.settings.internal === true ? { ...parsed, kind: "engine.connect.custom.manual-save" } : null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function parseEngineConnectCustomDiscoveredFrame(value: unknown): EngineConnectCustomDiscoveredFrame | null {
|
|
190
|
+
const o = connectionObject(value, ["kind", "opId", "result"]);
|
|
191
|
+
if (!o || o.kind !== "engine.connect.custom.discovered" || !connectionOpId(o.opId)) return null;
|
|
192
|
+
const error = connectionObject(o.result, ["ok", "reason"]);
|
|
193
|
+
if (error?.ok === false && isCustomConnectionReason(error.reason)) return { kind: o.kind, opId: o.opId, result: { ok: false, reason: error.reason } };
|
|
194
|
+
const result = connectionObject(o.result, ["ok", "models"], ["truncated"]);
|
|
195
|
+
if (!result || result.ok !== true || (result.truncated !== undefined && typeof result.truncated !== "boolean") || !Array.isArray(result.models) || result.models.length > 2000 || new Set(result.models).size !== result.models.length || !result.models.every(x => typeof x === "string" && x.length > 0 && x.length <= 200 && !/[\s\u0000-\u001f\u007f]/.test(x))) return null;
|
|
196
|
+
return { kind: o.kind, opId: o.opId, result: { ok: true, models: [...result.models], ...(result.truncated === true ? { truncated: true } : {}) } };
|
|
197
|
+
}
|
|
198
|
+
export function parseEngineConnectCustomSavedFrame(value: unknown): EngineConnectCustomSavedFrame | null {
|
|
199
|
+
const o = connectionObject(value, ["kind", "opId", "result"]);
|
|
200
|
+
if (!o || o.kind !== "engine.connect.custom.saved" || !connectionOpId(o.opId)) return null;
|
|
201
|
+
const error = connectionObject(o.result, ["ok", "reason"]);
|
|
202
|
+
if (error?.ok === false && isCustomConnectionReason(error.reason)) return { kind: o.kind, opId: o.opId, result: { ok: false, reason: error.reason } };
|
|
203
|
+
const result = connectionObject(o.result, ["ok", "capture", "authMode"], ["budget"]);
|
|
204
|
+
const capture = result ? parseCaptureResultWire(result.capture) : null;
|
|
205
|
+
const budget = result?.budget === undefined ? undefined : parseOpenRouterBudgetResult(result.budget);
|
|
206
|
+
return result?.ok === true && capture && (result.authMode === "bearer" || result.authMode === "x-api-key") && budget !== null ? { kind: o.kind, opId: o.opId, result: { ok: true, capture, authMode: result.authMode, ...(budget ? { budget } : {}) } } : null;
|
|
207
|
+
}
|