@intx/db 0.1.2 → 0.2.2
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/LICENSE +176 -0
- package/README.md +21 -2
- package/dist/asset-resolution.d.ts +52 -0
- package/dist/asset-resolution.js +66 -0
- package/dist/catalog-resolution.d.ts +76 -0
- package/dist/catalog-resolution.js +151 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.js +18 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +16 -0
- package/dist/connection.d.ts +3 -0
- package/dist/connection.js +23 -0
- package/dist/credential-resolution.d.ts +110 -0
- package/dist/credential-resolution.js +116 -0
- package/dist/grant-store.d.ts +3 -0
- package/dist/grant-store.js +37 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/migrate.d.ts +36 -0
- package/dist/migrate.js +135 -0
- package/dist/model-source-resolution.d.ts +69 -0
- package/dist/model-source-resolution.js +165 -0
- package/dist/parse-row.d.ts +193 -0
- package/dist/parse-row.js +141 -0
- package/dist/pg-error.d.ts +3 -0
- package/dist/pg-error.js +19 -0
- package/dist/pricing.d.ts +15 -0
- package/dist/pricing.js +24 -0
- package/dist/schema/agent-assets.d.ts +109 -0
- package/dist/schema/agent-assets.js +15 -0
- package/dist/schema/agents.d.ts +405 -0
- package/dist/schema/agents.js +57 -0
- package/dist/schema/assets.d.ts +143 -0
- package/{src/schema/assets.ts → dist/schema/assets.js} +7 -16
- package/dist/schema/auth.d.ts +606 -0
- package/dist/schema/auth.js +47 -0
- package/dist/schema/catalog.d.ts +794 -0
- package/dist/schema/catalog.js +102 -0
- package/dist/schema/column-types.d.ts +26 -0
- package/dist/schema/column-types.js +15 -0
- package/dist/schema/credentials.d.ts +305 -0
- package/{src/schema/credentials.ts → dist/schema/credentials.js} +16 -22
- package/dist/schema/git-tokens.d.ts +256 -0
- package/{src/schema/git-tokens.ts → dist/schema/git-tokens.js} +15 -38
- package/dist/schema/grants.d.ts +211 -0
- package/dist/schema/grants.js +24 -0
- package/dist/schema/index.d.ts +21 -0
- package/dist/schema/index.js +21 -0
- package/dist/schema/instances.d.ts +262 -0
- package/dist/schema/instances.js +39 -0
- package/dist/schema/messages.d.ts +414 -0
- package/dist/schema/messages.js +71 -0
- package/dist/schema/oauth-clients.d.ts +246 -0
- package/{src/schema/oauth-clients.ts → dist/schema/oauth-clients.js} +8 -14
- package/dist/schema/offerings.d.ts +160 -0
- package/dist/schema/offerings.js +18 -0
- package/dist/schema/principals.d.ts +128 -0
- package/{src/schema/principals.ts → dist/schema/principals.js} +7 -14
- package/dist/schema/providers.d.ts +220 -0
- package/{src/schema/providers.ts → dist/schema/providers.js} +5 -11
- package/dist/schema/roles.d.ts +242 -0
- package/dist/schema/roles.js +33 -0
- package/dist/schema/session-assets.d.ts +129 -0
- package/dist/schema/session-assets.js +28 -0
- package/dist/schema/sessions.d.ts +143 -0
- package/dist/schema/sessions.js +24 -0
- package/dist/schema/sidecar.d.ts +128 -0
- package/dist/schema/sidecar.js +18 -0
- package/dist/schema/tenants.d.ts +235 -0
- package/{src/schema/tenants.ts → dist/schema/tenants.js} +10 -27
- package/dist/schema/wallets.d.ts +354 -0
- package/dist/schema/wallets.js +41 -0
- package/dist/schema/workflow-deployments.d.ts +129 -0
- package/dist/schema/workflow-deployments.js +37 -0
- package/dist/tenant-hierarchy.d.ts +22 -0
- package/dist/tenant-hierarchy.js +55 -0
- package/migrations/0029_loose_warlock.sql +1 -0
- package/migrations/0030_session_asset_audit_split.sql +5 -0
- package/migrations/0031_gigantic_nicolaos.sql +68 -0
- package/migrations/0032_lethal_misty_knight.sql +1 -0
- package/migrations/0033_old_payback.sql +1 -0
- package/migrations/0034_sleepy_songbird.sql +11 -0
- package/migrations/0035_violet_giant_man.sql +3 -0
- package/migrations/0036_sharp_the_executioner.sql +2 -0
- package/migrations/meta/0029_snapshot.json +2664 -0
- package/migrations/meta/0030_snapshot.json +2670 -0
- package/migrations/meta/0031_snapshot.json +3100 -0
- package/migrations/meta/0032_snapshot.json +3106 -0
- package/migrations/meta/0033_snapshot.json +3112 -0
- package/migrations/meta/0034_snapshot.json +3198 -0
- package/migrations/meta/0035_snapshot.json +3225 -0
- package/migrations/meta/0036_snapshot.json +3237 -0
- package/migrations/meta/_journal.json +56 -0
- package/package.json +22 -11
- package/drizzle.config.ts +0 -23
- package/src/client.ts +0 -24
- package/src/config.ts +0 -19
- package/src/connection.ts +0 -27
- package/src/credential-resolution.ts +0 -378
- package/src/grant-store.ts +0 -51
- package/src/index.ts +0 -32
- package/src/migrate.test.ts +0 -35
- package/src/migrate.ts +0 -168
- package/src/parse-row.test.ts +0 -113
- package/src/parse-row.ts +0 -185
- package/src/schema/agent-assets.ts +0 -21
- package/src/schema/agents.ts +0 -49
- package/src/schema/auth.ts +0 -51
- package/src/schema/grants.ts +0 -26
- package/src/schema/index.ts +0 -19
- package/src/schema/instances.ts +0 -36
- package/src/schema/messages.ts +0 -108
- package/src/schema/offerings.ts +0 -20
- package/src/schema/roles.ts +0 -51
- package/src/schema/session-assets.ts +0 -49
- package/src/schema/sessions.ts +0 -26
- package/src/schema/sidecar.ts +0 -14
- package/src/schema/wallets.ts +0 -44
- package/src/tenant-hierarchy.ts +0 -34
- package/tsconfig.json +0 -4
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import postgres from "postgres";
|
|
2
|
+
function quoteIdentifier(name) {
|
|
3
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
4
|
+
}
|
|
5
|
+
export function createConnection(config) {
|
|
6
|
+
return postgres({
|
|
7
|
+
host: config.host,
|
|
8
|
+
port: config.port,
|
|
9
|
+
user: config.user,
|
|
10
|
+
password: config.password,
|
|
11
|
+
database: config.database,
|
|
12
|
+
max: config.max ?? 10,
|
|
13
|
+
...(config.ssl !== undefined && { ssl: config.ssl }),
|
|
14
|
+
...(config.schema !== undefined && {
|
|
15
|
+
// Pin the connection's search_path so unqualified table
|
|
16
|
+
// references resolve to the caller's schema. The migration
|
|
17
|
+
// runner emits SQL with the schema baked into FK references,
|
|
18
|
+
// but ORM-issued queries bind table names without a schema
|
|
19
|
+
// qualifier and rely on this setting.
|
|
20
|
+
connection: { search_path: quoteIdentifier(config.schema) },
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { DB } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a provider by name, walking up the tenant hierarchy.
|
|
4
|
+
* Returns the first match (child shadows parent).
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveProviderByName(db: DB["db"], tenantId: string, name: string): Promise<{
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
createdAt: Date;
|
|
10
|
+
updatedAt: Date;
|
|
11
|
+
tenantId: string;
|
|
12
|
+
plugin: string;
|
|
13
|
+
authorizationUrl: string | null;
|
|
14
|
+
tokenUrl: string | null;
|
|
15
|
+
userInfoUrl: string | null;
|
|
16
|
+
scopes: string[] | null;
|
|
17
|
+
metadata: unknown;
|
|
18
|
+
} | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Resolves an OAuth client for a provider, walking up the tenant hierarchy.
|
|
21
|
+
* Returns the first match (child shadows parent).
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveOAuthClient(db: DB["db"], tenantId: string, providerId: string): Promise<{
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
createdAt: Date;
|
|
27
|
+
updatedAt: Date;
|
|
28
|
+
providerId: string;
|
|
29
|
+
tenantId: string;
|
|
30
|
+
metadata: unknown;
|
|
31
|
+
clientId: string;
|
|
32
|
+
clientSecret: string;
|
|
33
|
+
redirectUris: string[] | null;
|
|
34
|
+
defaultScopes: string[] | null;
|
|
35
|
+
} | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a credential by name, walking up the tenant hierarchy.
|
|
38
|
+
* Returns the first match (child shadows parent).
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveCredentialByName(db: DB["db"], tenantId: string, name: string): Promise<{
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
createdAt: Date;
|
|
44
|
+
updatedAt: Date;
|
|
45
|
+
expiresAt: Date | null;
|
|
46
|
+
providerId: string;
|
|
47
|
+
tenantId: string;
|
|
48
|
+
status: "active" | "expired" | "revoked" | "error";
|
|
49
|
+
description: string | null;
|
|
50
|
+
principalId: string | null;
|
|
51
|
+
scopes: string[] | null;
|
|
52
|
+
metadata: unknown;
|
|
53
|
+
oauthClientId: string | null;
|
|
54
|
+
type: "api_key" | "oauth_token" | "certificate" | "other";
|
|
55
|
+
secret: string;
|
|
56
|
+
refreshSecret: string | null;
|
|
57
|
+
} | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Resolves a credential by ID, validating that it belongs to the
|
|
60
|
+
* given tenant or one of its ancestors.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveCredentialById(db: DB["db"], tenantId: string, credentialId: string): Promise<{
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
createdAt: Date;
|
|
66
|
+
updatedAt: Date;
|
|
67
|
+
expiresAt: Date | null;
|
|
68
|
+
providerId: string;
|
|
69
|
+
tenantId: string;
|
|
70
|
+
status: "active" | "expired" | "revoked" | "error";
|
|
71
|
+
description: string | null;
|
|
72
|
+
principalId: string | null;
|
|
73
|
+
scopes: string[] | null;
|
|
74
|
+
metadata: unknown;
|
|
75
|
+
oauthClientId: string | null;
|
|
76
|
+
type: "api_key" | "oauth_token" | "certificate" | "other";
|
|
77
|
+
secret: string;
|
|
78
|
+
refreshSecret: string | null;
|
|
79
|
+
} | null>;
|
|
80
|
+
type CredentialRequirement = {
|
|
81
|
+
providerName: string;
|
|
82
|
+
scopes?: string[];
|
|
83
|
+
source: "tenant" | "creator" | "invoker";
|
|
84
|
+
name?: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Resolves a credential matching an agent definition requirement.
|
|
88
|
+
* Used at agent launch time by the control plane to satisfy a definition's
|
|
89
|
+
* tool and integration credentials (inference sources resolve through the
|
|
90
|
+
* catalog, not this path).
|
|
91
|
+
*/
|
|
92
|
+
export declare function resolveCredentialRequirement(db: DB["db"], tenantId: string, requirement: CredentialRequirement, creatorPrincipalId: string | null, invokerPrincipalId: string | null): Promise<{
|
|
93
|
+
id: string;
|
|
94
|
+
name: string;
|
|
95
|
+
createdAt: Date;
|
|
96
|
+
updatedAt: Date;
|
|
97
|
+
expiresAt: Date | null;
|
|
98
|
+
providerId: string;
|
|
99
|
+
tenantId: string;
|
|
100
|
+
status: "active" | "expired" | "revoked" | "error";
|
|
101
|
+
description: string | null;
|
|
102
|
+
principalId: string | null;
|
|
103
|
+
scopes: string[] | null;
|
|
104
|
+
metadata: unknown;
|
|
105
|
+
oauthClientId: string | null;
|
|
106
|
+
type: "api_key" | "oauth_token" | "certificate" | "other";
|
|
107
|
+
secret: string;
|
|
108
|
+
refreshSecret: string | null;
|
|
109
|
+
} | null>;
|
|
110
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { eq, and, isNull } from "drizzle-orm";
|
|
2
|
+
import { credential } from "./schema/credentials.js";
|
|
3
|
+
import { oauthClient } from "./schema/oauth-clients.js";
|
|
4
|
+
import { provider } from "./schema/providers.js";
|
|
5
|
+
import { getAncestorChain } from "./tenant-hierarchy.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolves a provider by name, walking up the tenant hierarchy.
|
|
8
|
+
* Returns the first match (child shadows parent).
|
|
9
|
+
*/
|
|
10
|
+
export async function resolveProviderByName(db, tenantId, name) {
|
|
11
|
+
const chain = await getAncestorChain(db, tenantId);
|
|
12
|
+
for (const tid of chain) {
|
|
13
|
+
const row = await db.query.provider.findFirst({
|
|
14
|
+
where: and(eq(provider.tenantId, tid), eq(provider.name, name)),
|
|
15
|
+
});
|
|
16
|
+
if (row)
|
|
17
|
+
return row;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolves an OAuth client for a provider, walking up the tenant hierarchy.
|
|
23
|
+
* Returns the first match (child shadows parent).
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveOAuthClient(db, tenantId, providerId) {
|
|
26
|
+
const chain = await getAncestorChain(db, tenantId);
|
|
27
|
+
for (const tid of chain) {
|
|
28
|
+
const row = await db.query.oauthClient.findFirst({
|
|
29
|
+
where: and(eq(oauthClient.tenantId, tid), eq(oauthClient.providerId, providerId)),
|
|
30
|
+
});
|
|
31
|
+
if (row)
|
|
32
|
+
return row;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a credential by name, walking up the tenant hierarchy.
|
|
38
|
+
* Returns the first match (child shadows parent).
|
|
39
|
+
*/
|
|
40
|
+
export async function resolveCredentialByName(db, tenantId, name) {
|
|
41
|
+
const chain = await getAncestorChain(db, tenantId);
|
|
42
|
+
for (const tid of chain) {
|
|
43
|
+
const row = await db.query.credential.findFirst({
|
|
44
|
+
where: and(eq(credential.tenantId, tid), eq(credential.name, name)),
|
|
45
|
+
});
|
|
46
|
+
if (row)
|
|
47
|
+
return row;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolves a credential by ID, validating that it belongs to the
|
|
53
|
+
* given tenant or one of its ancestors.
|
|
54
|
+
*/
|
|
55
|
+
export async function resolveCredentialById(db, tenantId, credentialId) {
|
|
56
|
+
const row = await db.query.credential.findFirst({
|
|
57
|
+
where: eq(credential.id, credentialId),
|
|
58
|
+
});
|
|
59
|
+
if (!row)
|
|
60
|
+
return null;
|
|
61
|
+
const chain = await getAncestorChain(db, tenantId);
|
|
62
|
+
if (!chain.includes(row.tenantId))
|
|
63
|
+
return null;
|
|
64
|
+
return row;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolves a credential matching an agent definition requirement.
|
|
68
|
+
* Used at agent launch time by the control plane to satisfy a definition's
|
|
69
|
+
* tool and integration credentials (inference sources resolve through the
|
|
70
|
+
* catalog, not this path).
|
|
71
|
+
*/
|
|
72
|
+
export async function resolveCredentialRequirement(db, tenantId, requirement, creatorPrincipalId, invokerPrincipalId) {
|
|
73
|
+
const resolvedProvider = await resolveProviderByName(db, tenantId, requirement.providerName);
|
|
74
|
+
if (!resolvedProvider)
|
|
75
|
+
return null;
|
|
76
|
+
const chain = await getAncestorChain(db, tenantId);
|
|
77
|
+
const principalFilter = requirement.source === "tenant"
|
|
78
|
+
? null
|
|
79
|
+
: requirement.source === "creator"
|
|
80
|
+
? creatorPrincipalId
|
|
81
|
+
: invokerPrincipalId;
|
|
82
|
+
for (const tid of chain) {
|
|
83
|
+
const conditions = [
|
|
84
|
+
eq(credential.tenantId, tid),
|
|
85
|
+
eq(credential.providerId, resolvedProvider.id),
|
|
86
|
+
eq(credential.status, "active"),
|
|
87
|
+
];
|
|
88
|
+
if (principalFilter === null) {
|
|
89
|
+
conditions.push(isNull(credential.principalId));
|
|
90
|
+
}
|
|
91
|
+
else if (principalFilter) {
|
|
92
|
+
conditions.push(eq(credential.principalId, principalFilter));
|
|
93
|
+
}
|
|
94
|
+
if (requirement.name) {
|
|
95
|
+
conditions.push(eq(credential.name, requirement.name));
|
|
96
|
+
}
|
|
97
|
+
const rows = await db.query.credential.findMany({
|
|
98
|
+
where: and(...conditions),
|
|
99
|
+
});
|
|
100
|
+
const matching = rows.filter((row) => {
|
|
101
|
+
if (!requirement.scopes || requirement.scopes.length === 0)
|
|
102
|
+
return true;
|
|
103
|
+
const rowScopes = row.scopes ?? [];
|
|
104
|
+
return requirement.scopes.every((s) => rowScopes.includes(s));
|
|
105
|
+
});
|
|
106
|
+
const [sole] = matching;
|
|
107
|
+
if (matching.length === 1 && sole)
|
|
108
|
+
return sole;
|
|
109
|
+
if (matching.length > 1) {
|
|
110
|
+
throw new Error(`Ambiguous credential match: ${matching.length} credentials match ` +
|
|
111
|
+
`provider=${requirement.providerName} source=${requirement.source} ` +
|
|
112
|
+
`in tenant ${tid}. Specify a name to disambiguate.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { eq, and, or, isNull, gt, inArray } from "drizzle-orm";
|
|
2
|
+
import { grant } from "./schema/grants.js";
|
|
3
|
+
import { principalRole } from "./schema/roles.js";
|
|
4
|
+
import { parseGrantRow } from "./parse-row.js";
|
|
5
|
+
function toGrantRule(row) {
|
|
6
|
+
const parsed = parseGrantRow(row);
|
|
7
|
+
return {
|
|
8
|
+
id: parsed.id,
|
|
9
|
+
resource: parsed.resource,
|
|
10
|
+
action: parsed.action,
|
|
11
|
+
effect: parsed.effect,
|
|
12
|
+
origin: parsed.origin,
|
|
13
|
+
conditions: parsed.conditions,
|
|
14
|
+
expiresAt: parsed.expiresAt,
|
|
15
|
+
roleId: parsed.roleId,
|
|
16
|
+
principalId: parsed.principalId,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function createGrantStore(db) {
|
|
20
|
+
return {
|
|
21
|
+
async collectGrants(principalId, tenantId) {
|
|
22
|
+
const roleAssignments = await db.query.principalRole.findMany({
|
|
23
|
+
where: eq(principalRole.principalId, principalId),
|
|
24
|
+
});
|
|
25
|
+
const roleIds = roleAssignments.map((a) => a.roleId);
|
|
26
|
+
const now = new Date();
|
|
27
|
+
const ownership = [eq(grant.principalId, principalId)];
|
|
28
|
+
if (roleIds.length > 0) {
|
|
29
|
+
ownership.push(inArray(grant.roleId, roleIds));
|
|
30
|
+
}
|
|
31
|
+
const rows = await db.query.grant.findMany({
|
|
32
|
+
where: and(eq(grant.tenantId, tenantId), or(...ownership), or(isNull(grant.expiresAt), gt(grant.expiresAt, now))),
|
|
33
|
+
});
|
|
34
|
+
return rows.map(toGrantRule);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { createDB, type DB } from "./client.js";
|
|
2
|
+
export { pgErrorCode, PG_UNIQUE_VIOLATION, PG_FOREIGN_KEY_VIOLATION, } from "./pg-error.js";
|
|
3
|
+
export type { DBConfig } from "./config.js";
|
|
4
|
+
export { runMigrations, dropSchema } from "./migrate.js";
|
|
5
|
+
export { createGrantStore } from "./grant-store.js";
|
|
6
|
+
export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
|
|
7
|
+
export { resolveActivePrice, type ModelPricingRow } from "./pricing.js";
|
|
8
|
+
export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, } from "./credential-resolution.js";
|
|
9
|
+
export { resolveAssetByName, resolveAssetById, listAssetsForTenant, type AssetRow, type AssetWithOrigin, } from "./asset-resolution.js";
|
|
10
|
+
export { listVisibleModels, listVisibleProviders, listVisibleOfferings, type ModelRow, type ModelProviderRow, type ModelOfferingRow, type Origin, type VisibleModel, type VisibleProvider, type ResolvedOffering, } from "./catalog-resolution.js";
|
|
11
|
+
export { resolveModelSources, resolveInstanceModelSources, type CatalogSourceResolution, type SourceSkip, } from "./model-source-resolution.js";
|
|
12
|
+
export { parseAgentRow, parseAgentVersionRow, parseGrantRow, parseOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
|
|
13
|
+
export * as schema from "./schema/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { createDB } from "./client.js";
|
|
2
|
+
export { pgErrorCode, PG_UNIQUE_VIOLATION, PG_FOREIGN_KEY_VIOLATION, } from "./pg-error.js";
|
|
3
|
+
export { runMigrations, dropSchema } from "./migrate.js";
|
|
4
|
+
export { createGrantStore } from "./grant-store.js";
|
|
5
|
+
export { getAncestorChain, getDescendantTenants } from "./tenant-hierarchy.js";
|
|
6
|
+
export { resolveActivePrice } from "./pricing.js";
|
|
7
|
+
export { resolveProviderByName, resolveOAuthClient, resolveCredentialByName, resolveCredentialById, resolveCredentialRequirement, } from "./credential-resolution.js";
|
|
8
|
+
export { resolveAssetByName, resolveAssetById, listAssetsForTenant, } from "./asset-resolution.js";
|
|
9
|
+
export { listVisibleModels, listVisibleProviders, listVisibleOfferings, } from "./catalog-resolution.js";
|
|
10
|
+
export { resolveModelSources, resolveInstanceModelSources, } from "./model-source-resolution.js";
|
|
11
|
+
export { parseAgentRow, parseAgentVersionRow, parseGrantRow, parseOfferingRow, parseCredentialRow, parseProviderRow, parseTenantRow, parseWalletRow, parseTransactionRow, parseOAuthClientRow, parseGitTokenRow, parseTurnPartType, } from "./parse-row.js";
|
|
12
|
+
export * as schema from "./schema/index.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite drizzle-generated schema-qualified references like
|
|
3
|
+
* `REFERENCES "public"."<table>"` so they point at the target
|
|
4
|
+
* schema. The pattern matches `"public".` only when followed by
|
|
5
|
+
* another quoted identifier, leaving bare `"public"` inside string
|
|
6
|
+
* literals alone.
|
|
7
|
+
*/
|
|
8
|
+
export declare function rewriteSchemaQualifiedReferences(sql: string, schemaIdent: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Apply the drizzle-generated migration SQL into the given postgres
|
|
11
|
+
* schema. The schema is created if absent; nothing else is dropped.
|
|
12
|
+
*
|
|
13
|
+
* The migration source files reference the destination schema
|
|
14
|
+
* literally (e.g. `"public"."user"` in FK constraints). To make the
|
|
15
|
+
* same source apply cleanly into an arbitrary schema, we substitute
|
|
16
|
+
* the literal token `"public"` with the quoted target schema before
|
|
17
|
+
* executing each file. This is a textual substitution rather than a
|
|
18
|
+
* postgres-side `search_path` trick because the FK references are
|
|
19
|
+
* fully qualified; `search_path` would not redirect them.
|
|
20
|
+
*
|
|
21
|
+
* The substitution is bounded: the migration source is
|
|
22
|
+
* machine-generated and never contains the string `"public"` other
|
|
23
|
+
* than in schema-qualified identifiers, so there is no ambiguity to
|
|
24
|
+
* worry about. The substitution is a no-op when the target schema is
|
|
25
|
+
* literally `public`, which is the common case.
|
|
26
|
+
*/
|
|
27
|
+
export declare function runMigrations(configRaw: unknown, options: {
|
|
28
|
+
schema: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Drop the given schema along with everything in it. Used by the
|
|
32
|
+
* integration-test harness to tear down per-test schemas.
|
|
33
|
+
*/
|
|
34
|
+
export declare function dropSchema(configRaw: unknown, options: {
|
|
35
|
+
schema: string;
|
|
36
|
+
}): Promise<void>;
|
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { type } from "arktype";
|
|
4
|
+
import postgres from "postgres";
|
|
5
|
+
import { DBConfig } from "./config.js";
|
|
6
|
+
// Resolution of the migrations directory: the package layout pins
|
|
7
|
+
// the drizzle-generated SQL at `<pkgRoot>/migrations`. This file
|
|
8
|
+
// lives at `<pkgRoot>/src/migrate.ts`, so the directory is one level
|
|
9
|
+
// up from `__dirname`. Resolving via `import.meta` keeps the runtime
|
|
10
|
+
// honest about where it is reading SQL from instead of relying on a
|
|
11
|
+
// cwd-relative path that breaks under callers that change `cwd`.
|
|
12
|
+
const MIGRATIONS_DIR = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..", "migrations");
|
|
13
|
+
function quoteIdentifier(name) {
|
|
14
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Rewrite drizzle-generated schema-qualified references like
|
|
18
|
+
* `REFERENCES "public"."<table>"` so they point at the target
|
|
19
|
+
* schema. The pattern matches `"public".` only when followed by
|
|
20
|
+
* another quoted identifier, leaving bare `"public"` inside string
|
|
21
|
+
* literals alone.
|
|
22
|
+
*/
|
|
23
|
+
export function rewriteSchemaQualifiedReferences(sql, schemaIdent) {
|
|
24
|
+
return sql.replace(/"public"\.(?=")/g, `${schemaIdent}.`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Construct the single-connection postgres client used by the
|
|
28
|
+
* migration and teardown entry points. Both paths share the same SSL
|
|
29
|
+
* passthrough and suppress NOTICE-level diagnostics (cascade reports,
|
|
30
|
+
* "schema already exists") that postgres.js logs by default but are
|
|
31
|
+
* informational only in this harness context. The optional `searchPath`
|
|
32
|
+
* pins `search_path` on connection-open so unqualified `CREATE TABLE`
|
|
33
|
+
* statements land in the caller's schema.
|
|
34
|
+
*/
|
|
35
|
+
function createMigrationClient(config, searchPath) {
|
|
36
|
+
return postgres({
|
|
37
|
+
host: config.host,
|
|
38
|
+
port: config.port,
|
|
39
|
+
user: config.user,
|
|
40
|
+
password: config.password,
|
|
41
|
+
database: config.database,
|
|
42
|
+
max: 1,
|
|
43
|
+
onnotice: () => undefined,
|
|
44
|
+
...(config.ssl !== undefined && { ssl: config.ssl }),
|
|
45
|
+
...(searchPath !== undefined && {
|
|
46
|
+
connection: { search_path: searchPath },
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Apply the drizzle-generated migration SQL into the given postgres
|
|
52
|
+
* schema. The schema is created if absent; nothing else is dropped.
|
|
53
|
+
*
|
|
54
|
+
* The migration source files reference the destination schema
|
|
55
|
+
* literally (e.g. `"public"."user"` in FK constraints). To make the
|
|
56
|
+
* same source apply cleanly into an arbitrary schema, we substitute
|
|
57
|
+
* the literal token `"public"` with the quoted target schema before
|
|
58
|
+
* executing each file. This is a textual substitution rather than a
|
|
59
|
+
* postgres-side `search_path` trick because the FK references are
|
|
60
|
+
* fully qualified; `search_path` would not redirect them.
|
|
61
|
+
*
|
|
62
|
+
* The substitution is bounded: the migration source is
|
|
63
|
+
* machine-generated and never contains the string `"public"` other
|
|
64
|
+
* than in schema-qualified identifiers, so there is no ambiguity to
|
|
65
|
+
* worry about. The substitution is a no-op when the target schema is
|
|
66
|
+
* literally `public`, which is the common case.
|
|
67
|
+
*/
|
|
68
|
+
export async function runMigrations(configRaw, options) {
|
|
69
|
+
const config = DBConfig(configRaw);
|
|
70
|
+
if (config instanceof type.errors) {
|
|
71
|
+
throw new Error(`Invalid database config: ${config.summary}`);
|
|
72
|
+
}
|
|
73
|
+
const { schema } = options;
|
|
74
|
+
if (schema.length === 0) {
|
|
75
|
+
throw new Error("runMigrations: schema name must not be empty");
|
|
76
|
+
}
|
|
77
|
+
const schemaIdent = quoteIdentifier(schema);
|
|
78
|
+
// Pin search_path on the migration connection so unqualified
|
|
79
|
+
// `CREATE TABLE "name"` statements land in the target schema.
|
|
80
|
+
// The FK references in the source SQL are already schema-qualified
|
|
81
|
+
// (`"public"."user"`); we rewrite those to the target schema
|
|
82
|
+
// below. Together these two mechanisms route every object the
|
|
83
|
+
// migration touches into the caller's schema.
|
|
84
|
+
const sql = createMigrationClient(config, schemaIdent);
|
|
85
|
+
try {
|
|
86
|
+
// The CREATE SCHEMA must run on a connection that does not
|
|
87
|
+
// require the schema to already exist for search_path to be
|
|
88
|
+
// applied. postgres.js sets the GUC after connection-open, so
|
|
89
|
+
// CREATE SCHEMA IF NOT EXISTS here is the first statement and
|
|
90
|
+
// creates the schema before search_path matters.
|
|
91
|
+
await sql.unsafe(`CREATE SCHEMA IF NOT EXISTS ${schemaIdent}`);
|
|
92
|
+
const files = (await readdir(MIGRATIONS_DIR))
|
|
93
|
+
.filter((f) => f.endsWith(".sql"))
|
|
94
|
+
.sort();
|
|
95
|
+
if (files.length === 0) {
|
|
96
|
+
throw new Error(`runMigrations: no .sql files found in ${MIGRATIONS_DIR}`);
|
|
97
|
+
}
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
const raw = await readFile(path.join(MIGRATIONS_DIR, file), "utf-8");
|
|
100
|
+
const rendered = rewriteSchemaQualifiedReferences(raw, schemaIdent);
|
|
101
|
+
// drizzle emits multi-statement files separated by its own
|
|
102
|
+
// statement-breakpoint marker. Split on it and execute each
|
|
103
|
+
// statement individually so a syntax error in one statement
|
|
104
|
+
// surfaces with the right context.
|
|
105
|
+
const statements = rendered
|
|
106
|
+
.split("--> statement-breakpoint")
|
|
107
|
+
.map((s) => s.trim())
|
|
108
|
+
.filter((s) => s.length > 0);
|
|
109
|
+
for (const stmt of statements) {
|
|
110
|
+
await sql.unsafe(stmt);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await sql.end();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Drop the given schema along with everything in it. Used by the
|
|
120
|
+
* integration-test harness to tear down per-test schemas.
|
|
121
|
+
*/
|
|
122
|
+
export async function dropSchema(configRaw, options) {
|
|
123
|
+
const config = DBConfig(configRaw);
|
|
124
|
+
if (config instanceof type.errors) {
|
|
125
|
+
throw new Error(`Invalid database config: ${config.summary}`);
|
|
126
|
+
}
|
|
127
|
+
const sql = createMigrationClient(config);
|
|
128
|
+
try {
|
|
129
|
+
const schemaIdent = quoteIdentifier(options.schema);
|
|
130
|
+
await sql.unsafe(`DROP SCHEMA IF EXISTS ${schemaIdent} CASCADE`);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
await sql.end();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ModelRequirement, type ProviderPreference } from "@intx/types";
|
|
2
|
+
import type { InferenceSource } from "@intx/types/runtime";
|
|
3
|
+
import type { DB } from "./client.js";
|
|
4
|
+
/**
|
|
5
|
+
* Why a single offering could not be turned into a launchable source.
|
|
6
|
+
* `wallet_backed` providers are storable in the catalog but not launchable
|
|
7
|
+
* in this credential-backed-only release.
|
|
8
|
+
*/
|
|
9
|
+
export type SourceSkip = {
|
|
10
|
+
reason: "wallet_backed";
|
|
11
|
+
provider: string;
|
|
12
|
+
} | {
|
|
13
|
+
reason: "credential_unresolved";
|
|
14
|
+
provider: string;
|
|
15
|
+
} | {
|
|
16
|
+
reason: "provider_misconfigured";
|
|
17
|
+
provider: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Outcome of resolving an agent's model requirements to an ordered set of
|
|
21
|
+
* inference sources. The order is the routing order: the head is the
|
|
22
|
+
* default, the tail is the failover chain.
|
|
23
|
+
*
|
|
24
|
+
* `model_unavailable.skips` enumerates the offerings that were eligible for
|
|
25
|
+
* the model but could not produce a launchable source (wallet-backed, or an
|
|
26
|
+
* unresolvable credential). It is empty when no offering was eligible at all
|
|
27
|
+
* — the model is absent from the tenant catalog, or the capability and
|
|
28
|
+
* preference filters excluded every offering — a case the launch path can
|
|
29
|
+
* distinguish from a populated `skips` when explaining the failure.
|
|
30
|
+
*/
|
|
31
|
+
export type CatalogSourceResolution = {
|
|
32
|
+
ok: true;
|
|
33
|
+
sources: InferenceSource[];
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
reason: "no_requirements";
|
|
37
|
+
} | {
|
|
38
|
+
ok: false;
|
|
39
|
+
reason: "model_unavailable";
|
|
40
|
+
model: string;
|
|
41
|
+
skips: SourceSkip[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Resolves an agent's model requirements against the tenant catalog into an
|
|
45
|
+
* ordered `InferenceSource[]` for the harness.
|
|
46
|
+
*
|
|
47
|
+
* For each requirement, the tenant-visible offerings for the named model are
|
|
48
|
+
* filtered by the required capabilities, ordered by catalog priority, then
|
|
49
|
+
* reordered by the creator preference and finally the invoker preference
|
|
50
|
+
* (invoker preferences key on the canonical model name). Each surviving
|
|
51
|
+
* offering is resolved to a credential-backed source; offerings that cannot
|
|
52
|
+
* produce one are skipped. A required model that yields no source makes the
|
|
53
|
+
* agent unlaunchable.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveModelSources(db: DB["db"], tenantId: string, requirements: ModelRequirement[], opts?: {
|
|
56
|
+
invokerPreferences?: Record<string, ProviderPreference>;
|
|
57
|
+
}): Promise<CatalogSourceResolution>;
|
|
58
|
+
/**
|
|
59
|
+
* Resolves the ordered sources for a running instance from persisted state:
|
|
60
|
+
* the agent definition's model requirements and the invoker's launch-time
|
|
61
|
+
* preferences stored on the instance row. Launch, credential-rotation push,
|
|
62
|
+
* and sidecar reconnect all resolve through this, so a running instance's
|
|
63
|
+
* source list is a pure function of persisted state — re-resolution
|
|
64
|
+
* reproduces the launch ordering, including the invoker's reorder/restrict.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveInstanceModelSources(db: DB["db"], tenantId: string, instance: {
|
|
67
|
+
agentId: string;
|
|
68
|
+
modelPreferences: unknown;
|
|
69
|
+
}): Promise<CatalogSourceResolution>;
|