@jasonews/agent-squad-gateway 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +200 -0
- package/SECURITY.md +44 -0
- package/dist/bin/agent-squad-gateway.js +55 -0
- package/dist/bin/agent-squad-gateway.js.map +1 -0
- package/dist/config/config.js +124 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/paths.js +16 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/control-plane/clients.js +77 -0
- package/dist/control-plane/clients.js.map +1 -0
- package/dist/control-plane/core-connection.js +54 -0
- package/dist/control-plane/core-connection.js.map +1 -0
- package/dist/control-plane/credentials.js +134 -0
- package/dist/control-plane/credentials.js.map +1 -0
- package/dist/control-plane/db.js +33 -0
- package/dist/control-plane/db.js.map +1 -0
- package/dist/control-plane/extensions.js +59 -0
- package/dist/control-plane/extensions.js.map +1 -0
- package/dist/control-plane/grants.js +107 -0
- package/dist/control-plane/grants.js.map +1 -0
- package/dist/control-plane/idempotency.js +132 -0
- package/dist/control-plane/idempotency.js.map +1 -0
- package/dist/control-plane/migrations.js +133 -0
- package/dist/control-plane/migrations.js.map +1 -0
- package/dist/control-plane/response-sessions.js +359 -0
- package/dist/control-plane/response-sessions.js.map +1 -0
- package/dist/control-plane/retention.js +138 -0
- package/dist/control-plane/retention.js.map +1 -0
- package/dist/control-plane/runs.js +156 -0
- package/dist/control-plane/runs.js.map +1 -0
- package/dist/control-plane/targets.js +449 -0
- package/dist/control-plane/targets.js.map +1 -0
- package/dist/control-plane/types.js +2 -0
- package/dist/control-plane/types.js.map +1 -0
- package/dist/core-client/client.js +275 -0
- package/dist/core-client/client.js.map +1 -0
- package/dist/core-client/event-proxy.js +105 -0
- package/dist/core-client/event-proxy.js.map +1 -0
- package/dist/core-client/types.js +2 -0
- package/dist/core-client/types.js.map +1 -0
- package/dist/extensions/contract.js +2 -0
- package/dist/extensions/contract.js.map +1 -0
- package/dist/extensions/openai/auth.js +16 -0
- package/dist/extensions/openai/auth.js.map +1 -0
- package/dist/extensions/openai/chat.js +403 -0
- package/dist/extensions/openai/chat.js.map +1 -0
- package/dist/extensions/openai/errors.js +79 -0
- package/dist/extensions/openai/errors.js.map +1 -0
- package/dist/extensions/openai/image-input.js +79 -0
- package/dist/extensions/openai/image-input.js.map +1 -0
- package/dist/extensions/openai/models.js +40 -0
- package/dist/extensions/openai/models.js.map +1 -0
- package/dist/extensions/openai/responses.js +731 -0
- package/dist/extensions/openai/responses.js.map +1 -0
- package/dist/extensions/openai/routes.js +81 -0
- package/dist/extensions/openai/routes.js.map +1 -0
- package/dist/extensions/openai/run-attempt.js +96 -0
- package/dist/extensions/openai/run-attempt.js.map +1 -0
- package/dist/extensions/openai/schemas.js +176 -0
- package/dist/extensions/openai/schemas.js.map +1 -0
- package/dist/extensions/openai/sse.js +421 -0
- package/dist/extensions/openai/sse.js.map +1 -0
- package/dist/extensions/openai/tools.js +727 -0
- package/dist/extensions/openai/tools.js.map +1 -0
- package/dist/extensions/registry.js +30 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/lifecycle/daemon.js +437 -0
- package/dist/lifecycle/daemon.js.map +1 -0
- package/dist/lifecycle/pidfile.js +34 -0
- package/dist/lifecycle/pidfile.js.map +1 -0
- package/dist/provider-runtime/antigravity/adapter.js +398 -0
- package/dist/provider-runtime/antigravity/adapter.js.map +1 -0
- package/dist/provider-runtime/antigravity/conversation.js +31 -0
- package/dist/provider-runtime/antigravity/conversation.js.map +1 -0
- package/dist/provider-runtime/capability-service.js +191 -0
- package/dist/provider-runtime/capability-service.js.map +1 -0
- package/dist/provider-runtime/claude/adapter.js +397 -0
- package/dist/provider-runtime/claude/adapter.js.map +1 -0
- package/dist/provider-runtime/claude/protocol.js +143 -0
- package/dist/provider-runtime/claude/protocol.js.map +1 -0
- package/dist/provider-runtime/codex/adapter.js +440 -0
- package/dist/provider-runtime/codex/adapter.js.map +1 -0
- package/dist/provider-runtime/codex/json-rpc-client.js +182 -0
- package/dist/provider-runtime/codex/json-rpc-client.js.map +1 -0
- package/dist/provider-runtime/codex/protocol.js +128 -0
- package/dist/provider-runtime/codex/protocol.js.map +1 -0
- package/dist/provider-runtime/command-discovery.js +106 -0
- package/dist/provider-runtime/command-discovery.js.map +1 -0
- package/dist/provider-runtime/cursor/adapter.js +427 -0
- package/dist/provider-runtime/cursor/adapter.js.map +1 -0
- package/dist/provider-runtime/cursor/protocol.js +81 -0
- package/dist/provider-runtime/cursor/protocol.js.map +1 -0
- package/dist/provider-runtime/fake/adapter.js +138 -0
- package/dist/provider-runtime/fake/adapter.js.map +1 -0
- package/dist/provider-runtime/image-assets.js +409 -0
- package/dist/provider-runtime/image-assets.js.map +1 -0
- package/dist/provider-runtime/image-support.js +5 -0
- package/dist/provider-runtime/image-support.js.map +1 -0
- package/dist/provider-runtime/invocation-service.js +503 -0
- package/dist/provider-runtime/invocation-service.js.map +1 -0
- package/dist/provider-runtime/kimi/adapter.js +657 -0
- package/dist/provider-runtime/kimi/adapter.js.map +1 -0
- package/dist/provider-runtime/opencode/adapter.js +610 -0
- package/dist/provider-runtime/opencode/adapter.js.map +1 -0
- package/dist/provider-runtime/opencode/server.js +46 -0
- package/dist/provider-runtime/opencode/server.js.map +1 -0
- package/dist/provider-runtime/process/line-reader.js +49 -0
- package/dist/provider-runtime/process/line-reader.js.map +1 -0
- package/dist/provider-runtime/process/managed-process.js +151 -0
- package/dist/provider-runtime/process/managed-process.js.map +1 -0
- package/dist/provider-runtime/provider-input.js +77 -0
- package/dist/provider-runtime/provider-input.js.map +1 -0
- package/dist/provider-runtime/register-providers.js +26 -0
- package/dist/provider-runtime/register-providers.js.map +1 -0
- package/dist/provider-runtime/registry.js +35 -0
- package/dist/provider-runtime/registry.js.map +1 -0
- package/dist/provider-runtime/replay-buffer.js +168 -0
- package/dist/provider-runtime/replay-buffer.js.map +1 -0
- package/dist/provider-runtime/scheduler.js +155 -0
- package/dist/provider-runtime/scheduler.js.map +1 -0
- package/dist/provider-runtime/types.js +2 -0
- package/dist/provider-runtime/types.js.map +1 -0
- package/dist/provider-runtime/workspaces.js +814 -0
- package/dist/provider-runtime/workspaces.js.map +1 -0
- package/dist/security/admin-auth.js +109 -0
- package/dist/security/admin-auth.js.map +1 -0
- package/dist/security/crypto.js +52 -0
- package/dist/security/crypto.js.map +1 -0
- package/dist/security/secret-files.js +33 -0
- package/dist/security/secret-files.js.map +1 -0
- package/dist/server/admin-routes.js +610 -0
- package/dist/server/admin-routes.js.map +1 -0
- package/dist/server/app.js +178 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/auth-hooks.js +43 -0
- package/dist/server/auth-hooks.js.map +1 -0
- package/dist/server/core-routes.js +127 -0
- package/dist/server/core-routes.js.map +1 -0
- package/dist/server/errors.js +19 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/ui-routes.js +77 -0
- package/dist/server/ui-routes.js.map +1 -0
- package/dist/web/assets/index-BhqNoPpE.js +206 -0
- package/dist/web/assets/index-BhqNoPpE.js.map +1 -0
- package/dist/web/assets/index-C5nnaBfZ.css +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +74 -0
- package/translations/README.zh-CN.md +175 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
+
import { constantTimeDigestMatch, decryptValue, digest, encryptValue } from '../security/crypto.js';
|
|
3
|
+
const API_KEY = /^asqsk_([a-f0-9]{18})_([A-Za-z0-9_-]{43})$/;
|
|
4
|
+
function toCredentialRecord(row) {
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
clientId: row.client_id,
|
|
8
|
+
name: row.name,
|
|
9
|
+
prefix: row.prefix,
|
|
10
|
+
createdAt: row.created_at,
|
|
11
|
+
expiresAt: row.expires_at,
|
|
12
|
+
revokedAt: row.revoked_at,
|
|
13
|
+
lastUsedAt: row.last_used_at,
|
|
14
|
+
rotatedFrom: row.rotated_from,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export class CredentialService {
|
|
18
|
+
db;
|
|
19
|
+
masterKey;
|
|
20
|
+
constructor(db, masterKey) {
|
|
21
|
+
this.db = db;
|
|
22
|
+
this.masterKey = masterKey;
|
|
23
|
+
}
|
|
24
|
+
create(clientId, name, expiresAt = null) {
|
|
25
|
+
return this.createCredential(clientId, name, null, expiresAt);
|
|
26
|
+
}
|
|
27
|
+
list(clientId) {
|
|
28
|
+
const rows = clientId === undefined
|
|
29
|
+
? this.db.prepare(`
|
|
30
|
+
SELECT id, client_id, name, prefix, created_at, expires_at, revoked_at, last_used_at, rotated_from
|
|
31
|
+
FROM credentials
|
|
32
|
+
ORDER BY created_at DESC, rowid DESC
|
|
33
|
+
`).all()
|
|
34
|
+
: this.db.prepare(`
|
|
35
|
+
SELECT id, client_id, name, prefix, created_at, expires_at, revoked_at, last_used_at, rotated_from
|
|
36
|
+
FROM credentials
|
|
37
|
+
WHERE client_id = ?
|
|
38
|
+
ORDER BY created_at DESC, rowid DESC
|
|
39
|
+
`).all(clientId);
|
|
40
|
+
return rows.map(toCredentialRecord);
|
|
41
|
+
}
|
|
42
|
+
authenticate(apiKey) {
|
|
43
|
+
const parsed = API_KEY.exec(apiKey);
|
|
44
|
+
if (!parsed)
|
|
45
|
+
return null;
|
|
46
|
+
const keyId = parsed[1];
|
|
47
|
+
if (!keyId)
|
|
48
|
+
return null;
|
|
49
|
+
const credential = this.db.prepare(`
|
|
50
|
+
SELECT credentials.id, credentials.client_id, credentials.prefix, credentials.digest,
|
|
51
|
+
credentials.expires_at, credentials.revoked_at, clients.status AS client_status
|
|
52
|
+
FROM credentials
|
|
53
|
+
JOIN clients ON clients.id = credentials.client_id
|
|
54
|
+
WHERE credentials.key_id = ?
|
|
55
|
+
`).get(keyId);
|
|
56
|
+
if (!credential || !constantTimeDigestMatch(apiKey, credential.digest))
|
|
57
|
+
return null;
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
if (credential.client_status !== 'active'
|
|
60
|
+
|| credential.revoked_at !== null
|
|
61
|
+
|| (credential.expires_at !== null && credential.expires_at <= now)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const result = this.db.prepare(`
|
|
65
|
+
UPDATE credentials
|
|
66
|
+
SET last_used_at = ?
|
|
67
|
+
WHERE id = ?
|
|
68
|
+
AND revoked_at IS NULL
|
|
69
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
70
|
+
AND EXISTS (SELECT 1 FROM clients WHERE id = credentials.client_id AND status = 'active')
|
|
71
|
+
`).run(now, credential.id, now);
|
|
72
|
+
if (result.changes !== 1)
|
|
73
|
+
return null;
|
|
74
|
+
return {
|
|
75
|
+
credentialId: credential.id,
|
|
76
|
+
clientId: credential.client_id,
|
|
77
|
+
prefix: credential.prefix,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
reveal(id) {
|
|
81
|
+
const credential = this.db.prepare(`
|
|
82
|
+
SELECT ciphertext, nonce, auth_tag FROM credentials WHERE id = ?
|
|
83
|
+
`).get(id);
|
|
84
|
+
if (!credential)
|
|
85
|
+
throw new Error('credential_not_found');
|
|
86
|
+
return decryptValue({
|
|
87
|
+
ciphertext: credential.ciphertext,
|
|
88
|
+
nonce: credential.nonce,
|
|
89
|
+
authTag: credential.auth_tag,
|
|
90
|
+
}, this.masterKey);
|
|
91
|
+
}
|
|
92
|
+
revoke(id) {
|
|
93
|
+
const result = this.db.prepare(`
|
|
94
|
+
UPDATE credentials SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL
|
|
95
|
+
`).run(new Date().toISOString(), id);
|
|
96
|
+
if (result.changes === 0)
|
|
97
|
+
throw new Error('credential_not_found');
|
|
98
|
+
}
|
|
99
|
+
rotate(id, name, expiresAt = null) {
|
|
100
|
+
const createReplacement = this.db.transaction(() => {
|
|
101
|
+
const original = this.db.prepare('SELECT client_id FROM credentials WHERE id = ? AND revoked_at IS NULL').get(id);
|
|
102
|
+
if (!original)
|
|
103
|
+
throw new Error('credential_not_found');
|
|
104
|
+
const replacement = this.createCredential(original.client_id, name, id, expiresAt);
|
|
105
|
+
const revoked = this.db.prepare(`
|
|
106
|
+
UPDATE credentials SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL
|
|
107
|
+
`).run(new Date().toISOString(), id);
|
|
108
|
+
if (revoked.changes !== 1)
|
|
109
|
+
throw new Error('credential_not_found');
|
|
110
|
+
return replacement;
|
|
111
|
+
});
|
|
112
|
+
return createReplacement();
|
|
113
|
+
}
|
|
114
|
+
createCredential(clientId, name, rotatedFrom, expiresAt) {
|
|
115
|
+
const keyId = randomBytes(9).toString('hex');
|
|
116
|
+
const secret = randomBytes(32).toString('base64url');
|
|
117
|
+
const apiKey = `asqsk_${keyId}_${secret}`;
|
|
118
|
+
const envelope = encryptValue(apiKey, this.masterKey);
|
|
119
|
+
const created = {
|
|
120
|
+
id: `credential_${randomUUID()}`,
|
|
121
|
+
clientId,
|
|
122
|
+
prefix: `asqsk_${keyId}`,
|
|
123
|
+
apiKey,
|
|
124
|
+
};
|
|
125
|
+
this.db.prepare(`
|
|
126
|
+
INSERT INTO credentials (
|
|
127
|
+
id, key_id, client_id, name, prefix, digest, ciphertext, nonce, auth_tag,
|
|
128
|
+
created_at, expires_at, rotated_from
|
|
129
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
130
|
+
`).run(created.id, keyId, clientId, name, created.prefix, digest(apiKey), envelope.ciphertext, envelope.nonce, envelope.authTag, new Date().toISOString(), expiresAt, rotatedFrom);
|
|
131
|
+
return created;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/control-plane/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { MIGRATION_V1, TARGET_VERSION } from './migrations.js';
|
|
5
|
+
export class GatewayDb {
|
|
6
|
+
raw;
|
|
7
|
+
constructor(raw) {
|
|
8
|
+
this.raw = raw;
|
|
9
|
+
}
|
|
10
|
+
prepare(sql) {
|
|
11
|
+
return this.raw.prepare(sql);
|
|
12
|
+
}
|
|
13
|
+
transaction(fn) {
|
|
14
|
+
return this.raw.transaction(fn);
|
|
15
|
+
}
|
|
16
|
+
close() {
|
|
17
|
+
this.raw.close();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function openGatewayDb(path) {
|
|
21
|
+
if (path !== ':memory:')
|
|
22
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
23
|
+
const sqlite = new Database(path);
|
|
24
|
+
sqlite.pragma('foreign_keys = ON');
|
|
25
|
+
if (path !== ':memory:')
|
|
26
|
+
sqlite.pragma('journal_mode = WAL');
|
|
27
|
+
const current = sqlite.pragma('user_version', { simple: true });
|
|
28
|
+
if (current < 1)
|
|
29
|
+
sqlite.transaction(() => sqlite.exec(MIGRATION_V1))();
|
|
30
|
+
sqlite.pragma(`user_version = ${TARGET_VERSION}`);
|
|
31
|
+
return new GatewayDb(sqlite);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/control-plane/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE/D,MAAM,OAAO,SAAS;IACC;IAArB,YAAqB,GAAmB;QAAnB,QAAG,GAAH,GAAG,CAAgB;IAAG,CAAC;IAE5C,OAAO,CAAoD,GAAW;QACpE,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAoB,CAAC;IAClD,CAAC;IAED,WAAW,CAA0C,EAAK;QACxD,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAiB,CAAC;IAClD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;CACF;AAED,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,IAAI,KAAK,UAAU;QAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACnC,IAAI,IAAI,KAAK,UAAU;QAAE,MAAM,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAW,CAAC;IAC1E,IAAI,OAAO,GAAG,CAAC;QAAE,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;IACvE,MAAM,CAAC,MAAM,CAAC,kBAAkB,cAAc,EAAE,CAAC,CAAC;IAClD,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function toExtensionRecord(row) {
|
|
2
|
+
return {
|
|
3
|
+
id: row.id,
|
|
4
|
+
version: row.version,
|
|
5
|
+
enabled: row.enabled === 1,
|
|
6
|
+
createdAt: row.created_at,
|
|
7
|
+
updatedAt: row.updated_at,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export class ExtensionRepository {
|
|
11
|
+
db;
|
|
12
|
+
constructor(db) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
}
|
|
15
|
+
upsert(id, version, enabled) {
|
|
16
|
+
if (typeof id !== 'string' || id.trim().length === 0)
|
|
17
|
+
throw new Error('invalid_extension_id');
|
|
18
|
+
if (typeof version !== 'string' || version.trim().length === 0)
|
|
19
|
+
throw new Error('invalid_extension_version');
|
|
20
|
+
if (typeof enabled !== 'boolean')
|
|
21
|
+
throw new Error('invalid_extension_enabled');
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
this.db.prepare(`
|
|
24
|
+
INSERT INTO extensions (id, version, enabled, created_at, updated_at)
|
|
25
|
+
VALUES (?, ?, ?, ?, ?)
|
|
26
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
27
|
+
version = excluded.version,
|
|
28
|
+
enabled = excluded.enabled,
|
|
29
|
+
updated_at = excluded.updated_at
|
|
30
|
+
`).run(id, version, enabled ? 1 : 0, now, now);
|
|
31
|
+
return this.getRequired(id);
|
|
32
|
+
}
|
|
33
|
+
setEnabled(id, enabled) {
|
|
34
|
+
if (typeof enabled !== 'boolean')
|
|
35
|
+
throw new Error('invalid_extension_enabled');
|
|
36
|
+
const result = this.db.prepare(`
|
|
37
|
+
UPDATE extensions SET enabled = ?, updated_at = ? WHERE id = ?
|
|
38
|
+
`).run(enabled ? 1 : 0, new Date().toISOString(), id);
|
|
39
|
+
if (result.changes !== 1)
|
|
40
|
+
throw new Error('extension_not_found');
|
|
41
|
+
}
|
|
42
|
+
list() {
|
|
43
|
+
return this.db.prepare(`
|
|
44
|
+
SELECT id, version, enabled, created_at, updated_at
|
|
45
|
+
FROM extensions
|
|
46
|
+
ORDER BY created_at DESC, rowid DESC
|
|
47
|
+
`).all().map(toExtensionRecord);
|
|
48
|
+
}
|
|
49
|
+
getRequired(id) {
|
|
50
|
+
const row = this.db.prepare(`
|
|
51
|
+
SELECT id, version, enabled, created_at, updated_at
|
|
52
|
+
FROM extensions WHERE id = ?
|
|
53
|
+
`).get(id);
|
|
54
|
+
if (!row)
|
|
55
|
+
throw new Error('extension_not_found');
|
|
56
|
+
return toExtensionRecord(row);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=extensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../src/control-plane/extensions.ts"],"names":[],"mappings":"AAWA,SAAS,iBAAiB,CAAC,GAAiB;IAC1C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,CAAC;QAC1B,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,GAAG,CAAC,UAAU;KAC1B,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,mBAAmB;IACD;IAA7B,YAA6B,EAAa;QAAb,OAAE,GAAF,EAAE,CAAW;IAAG,CAAC;IAE9C,MAAM,CAAC,EAAU,EAAE,OAAe,EAAE,OAAgB;QAClD,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC9F,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC7G,IAAI,OAAO,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAE/E,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;KAOf,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAE/C,OAAO,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,UAAU,CAAC,EAAU,EAAE,OAAgB;QACrC,IAAI,OAAO,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;KAE9B,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACnE,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAmB;;;;KAIxC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAClC,CAAC;IAEO,WAAW,CAAC,EAAU;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAyB;;;KAGnD,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACX,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACjD,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { hasCurrentCompatibleCapability, toInvocationTarget, } from './targets.js';
|
|
2
|
+
function toGrantRecord(row) {
|
|
3
|
+
return {
|
|
4
|
+
clientId: row.client_id,
|
|
5
|
+
extensionId: row.extension_id,
|
|
6
|
+
targetId: row.target_id,
|
|
7
|
+
createdAt: row.created_at,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export class GrantRepository {
|
|
11
|
+
db;
|
|
12
|
+
constructor(db) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
}
|
|
15
|
+
grant(clientId, extensionId, targetId) {
|
|
16
|
+
const target = this.findTarget(targetId);
|
|
17
|
+
if (!target)
|
|
18
|
+
throw new Error('target_not_found');
|
|
19
|
+
this.requireClient(clientId);
|
|
20
|
+
this.requireExtension(extensionId);
|
|
21
|
+
this.db.prepare(`
|
|
22
|
+
INSERT INTO grants (client_id, extension_id, target_id, created_at)
|
|
23
|
+
VALUES (?, ?, ?, ?)
|
|
24
|
+
ON CONFLICT(client_id, extension_id, target_id) DO NOTHING
|
|
25
|
+
`).run(clientId, extensionId, target.id, new Date().toISOString());
|
|
26
|
+
}
|
|
27
|
+
revoke(clientId, extensionId, targetId) {
|
|
28
|
+
const target = this.findTarget(targetId);
|
|
29
|
+
if (!target)
|
|
30
|
+
throw new Error('target_not_found');
|
|
31
|
+
this.db.prepare(`
|
|
32
|
+
DELETE FROM grants WHERE client_id = ? AND extension_id = ? AND target_id = ?
|
|
33
|
+
`).run(clientId, extensionId, target.id);
|
|
34
|
+
}
|
|
35
|
+
listForClient(clientId) {
|
|
36
|
+
return this.db.prepare(`
|
|
37
|
+
SELECT client_id, extension_id, target_id, created_at
|
|
38
|
+
FROM grants
|
|
39
|
+
WHERE client_id = ?
|
|
40
|
+
ORDER BY created_at DESC, rowid DESC
|
|
41
|
+
`).all(clientId).map(toGrantRecord);
|
|
42
|
+
}
|
|
43
|
+
authorize(clientId, extensionId, modelId) {
|
|
44
|
+
const row = this.db.prepare(`
|
|
45
|
+
SELECT
|
|
46
|
+
t.id, t.aliases_json, t.cli, t.native_model, t.reasoning_effort, t.enabled,
|
|
47
|
+
t.isolation_level, t.streaming_mode, t.tool_bridge, t.max_concurrency, t.max_queue,
|
|
48
|
+
t.queue_timeout_ms, t.run_timeout_ms, t.fixed_workspace, t.capability_version,
|
|
49
|
+
t.capability_verified_at, t.capability_json, t.capability_error, t.created_at, t.updated_at,
|
|
50
|
+
c.status AS client_status, e.enabled AS extension_enabled
|
|
51
|
+
FROM grants AS g
|
|
52
|
+
JOIN clients AS c ON c.id = g.client_id
|
|
53
|
+
JOIN extensions AS e ON e.id = g.extension_id
|
|
54
|
+
JOIN invocation_targets AS t ON t.id = g.target_id
|
|
55
|
+
WHERE g.client_id = ?
|
|
56
|
+
AND g.extension_id = ?
|
|
57
|
+
AND (
|
|
58
|
+
t.id = ?
|
|
59
|
+
OR EXISTS (
|
|
60
|
+
SELECT 1
|
|
61
|
+
FROM json_each(CASE WHEN json_valid(t.aliases_json) THEN t.aliases_json ELSE '[]' END)
|
|
62
|
+
WHERE value = ?
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
LIMIT 1
|
|
66
|
+
`).get(clientId, extensionId, modelId, modelId);
|
|
67
|
+
if (!row)
|
|
68
|
+
throw new Error('authorization_denied');
|
|
69
|
+
const target = toInvocationTarget(row);
|
|
70
|
+
if (row.client_status !== 'active'
|
|
71
|
+
|| row.extension_enabled !== 1
|
|
72
|
+
|| !target.enabled
|
|
73
|
+
|| !hasCurrentCompatibleCapability(target)) {
|
|
74
|
+
throw new Error('authorization_denied');
|
|
75
|
+
}
|
|
76
|
+
return target;
|
|
77
|
+
}
|
|
78
|
+
findTarget(idOrAlias) {
|
|
79
|
+
const row = this.db.prepare(`
|
|
80
|
+
SELECT
|
|
81
|
+
id, aliases_json, cli, native_model, reasoning_effort, enabled,
|
|
82
|
+
isolation_level, streaming_mode, tool_bridge, max_concurrency, max_queue,
|
|
83
|
+
queue_timeout_ms, run_timeout_ms, fixed_workspace, capability_version,
|
|
84
|
+
capability_verified_at, capability_json, capability_error, created_at, updated_at
|
|
85
|
+
FROM invocation_targets
|
|
86
|
+
WHERE id = ?
|
|
87
|
+
OR EXISTS (
|
|
88
|
+
SELECT 1
|
|
89
|
+
FROM json_each(CASE WHEN json_valid(aliases_json) THEN aliases_json ELSE '[]' END)
|
|
90
|
+
WHERE value = ?
|
|
91
|
+
)
|
|
92
|
+
LIMIT 1
|
|
93
|
+
`).get(idOrAlias, idOrAlias);
|
|
94
|
+
return row ? toInvocationTarget(row) : null;
|
|
95
|
+
}
|
|
96
|
+
requireClient(id) {
|
|
97
|
+
const row = this.db.prepare('SELECT id FROM clients WHERE id = ?').get(id);
|
|
98
|
+
if (!row)
|
|
99
|
+
throw new Error('client_not_found');
|
|
100
|
+
}
|
|
101
|
+
requireExtension(id) {
|
|
102
|
+
const row = this.db.prepare('SELECT id FROM extensions WHERE id = ?').get(id);
|
|
103
|
+
if (!row)
|
|
104
|
+
throw new Error('extension_not_found');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.js","sourceRoot":"","sources":["../../src/control-plane/grants.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const METADATA_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
3
|
+
export class IdempotencyConflictError extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super('idempotency_conflict');
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function sha256(value) {
|
|
9
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
10
|
+
}
|
|
11
|
+
function serializeCanonicalJson(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object')
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return `[${value.map(serializeCanonicalJson).join(',')}]`;
|
|
16
|
+
}
|
|
17
|
+
const entries = Object.entries(value)
|
|
18
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
19
|
+
.map(([key, child]) => `${JSON.stringify(key)}:${serializeCanonicalJson(child)}`);
|
|
20
|
+
return `{${entries.join(',')}}`;
|
|
21
|
+
}
|
|
22
|
+
export function canonicalizeRequest(value) {
|
|
23
|
+
const json = JSON.stringify(value);
|
|
24
|
+
if (json === undefined)
|
|
25
|
+
return 'null';
|
|
26
|
+
return serializeCanonicalJson(JSON.parse(json));
|
|
27
|
+
}
|
|
28
|
+
export class IdempotencyService {
|
|
29
|
+
db;
|
|
30
|
+
runs;
|
|
31
|
+
replay;
|
|
32
|
+
now;
|
|
33
|
+
metadataTtlMs;
|
|
34
|
+
constructor(db, runs, replay, options = {}) {
|
|
35
|
+
this.db = db;
|
|
36
|
+
this.runs = runs;
|
|
37
|
+
this.replay = replay;
|
|
38
|
+
this.now = options.now ?? Date.now;
|
|
39
|
+
this.metadataTtlMs = options.metadataTtlMs ?? METADATA_TTL_MS;
|
|
40
|
+
}
|
|
41
|
+
begin(input) {
|
|
42
|
+
const keyDigest = sha256(`${input.clientId}\0${input.endpoint}\0${input.key}`);
|
|
43
|
+
const requestHash = sha256(`${input.endpoint}\0${canonicalizeRequest(input.request)}`);
|
|
44
|
+
const decide = this.db.transaction(() => {
|
|
45
|
+
const existing = this.get(input.clientId, keyDigest);
|
|
46
|
+
if (existing && Date.parse(existing.expires_at) <= this.now()) {
|
|
47
|
+
this.db.prepare('DELETE FROM idempotency_keys WHERE client_id = ? AND key_digest = ?')
|
|
48
|
+
.run(input.clientId, keyDigest);
|
|
49
|
+
}
|
|
50
|
+
else if (existing) {
|
|
51
|
+
if (existing.request_hash !== requestHash)
|
|
52
|
+
throw new IdempotencyConflictError();
|
|
53
|
+
const base = this.decisionBase(existing);
|
|
54
|
+
if (!this.replay.available(existing.run_id)) {
|
|
55
|
+
return { ...base, type: 'unavailable' };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
...base,
|
|
59
|
+
type: existing.status === 'active' ? 'active_duplicate' : 'completed_replay',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
this.runs.create(input.run, input.runId);
|
|
63
|
+
this.db.prepare(`
|
|
64
|
+
INSERT INTO idempotency_keys (
|
|
65
|
+
client_id, key_digest, request_hash, run_id, response_id, status, expires_at
|
|
66
|
+
) VALUES (?, ?, ?, ?, ?, 'active', ?)
|
|
67
|
+
`).run(input.clientId, keyDigest, requestHash, input.runId, input.responseId ?? null, new Date(this.now() + this.metadataTtlMs).toISOString());
|
|
68
|
+
this.replay.open(input.runId);
|
|
69
|
+
return {
|
|
70
|
+
type: 'owner',
|
|
71
|
+
runId: input.runId,
|
|
72
|
+
responseId: input.responseId ?? null,
|
|
73
|
+
clientId: input.clientId,
|
|
74
|
+
keyDigest,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
return decide();
|
|
78
|
+
}
|
|
79
|
+
async attach(decision) {
|
|
80
|
+
if (decision.type === 'owner' || decision.type === 'unavailable') {
|
|
81
|
+
throw new Error(decision.type === 'unavailable' ? 'idempotency_replay_unavailable' : 'idempotency_owner_cannot_attach');
|
|
82
|
+
}
|
|
83
|
+
const chunks = [];
|
|
84
|
+
for await (const chunk of this.replay.subscribe(decision.runId))
|
|
85
|
+
chunks.push(chunk);
|
|
86
|
+
return chunks;
|
|
87
|
+
}
|
|
88
|
+
complete(decision, serialized) {
|
|
89
|
+
this.settle(decision, 'completed', serialized);
|
|
90
|
+
}
|
|
91
|
+
fail(decision, serialized) {
|
|
92
|
+
this.settle(decision, 'failed', serialized);
|
|
93
|
+
const run = this.runs.get(decision.runId);
|
|
94
|
+
if (run?.status === 'queued') {
|
|
95
|
+
this.runs.markQueuedFinished(decision.runId, 'failed', 'idempotency_owner_failed');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
settle(decision, status, serialized) {
|
|
99
|
+
if (decision.type !== 'owner')
|
|
100
|
+
throw new Error('idempotency_not_owner');
|
|
101
|
+
const result = this.db.prepare(`
|
|
102
|
+
UPDATE idempotency_keys SET status = ?
|
|
103
|
+
WHERE client_id = ? AND key_digest = ? AND run_id = ? AND status = 'active'
|
|
104
|
+
`).run(status, decision.clientId, decision.keyDigest, decision.runId);
|
|
105
|
+
if (result.changes !== 1)
|
|
106
|
+
throw new Error('invalid_idempotency_transition');
|
|
107
|
+
if (this.replay.available(decision.runId)) {
|
|
108
|
+
try {
|
|
109
|
+
this.replay.publish(decision.runId, serialized);
|
|
110
|
+
this.replay.complete(decision.runId);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
this.replay.evict(decision.runId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
get(clientId, keyDigest) {
|
|
118
|
+
return this.db.prepare(`
|
|
119
|
+
SELECT client_id, key_digest, request_hash, run_id, response_id, status, expires_at
|
|
120
|
+
FROM idempotency_keys WHERE client_id = ? AND key_digest = ?
|
|
121
|
+
`).get(clientId, keyDigest);
|
|
122
|
+
}
|
|
123
|
+
decisionBase(row) {
|
|
124
|
+
return {
|
|
125
|
+
runId: row.run_id,
|
|
126
|
+
responseId: row.response_id,
|
|
127
|
+
clientId: row.client_id,
|
|
128
|
+
keyDigest: row.key_digest,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../src/control-plane/idempotency.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export const MIGRATION_V1 = `
|
|
2
|
+
CREATE TABLE core_connection (
|
|
3
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
4
|
+
base_url TEXT NOT NULL,
|
|
5
|
+
last_status TEXT,
|
|
6
|
+
last_version TEXT,
|
|
7
|
+
last_checked_at TEXT
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE TABLE extensions (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
version TEXT NOT NULL,
|
|
13
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
config_json TEXT NOT NULL DEFAULT '{}',
|
|
15
|
+
created_at TEXT NOT NULL,
|
|
16
|
+
updated_at TEXT NOT NULL
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE TABLE invocation_targets (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
aliases_json TEXT NOT NULL DEFAULT '[]',
|
|
22
|
+
cli TEXT NOT NULL,
|
|
23
|
+
native_model TEXT NOT NULL,
|
|
24
|
+
reasoning_effort TEXT,
|
|
25
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
isolation_level TEXT NOT NULL CHECK (isolation_level IN ('strict','best_effort')),
|
|
27
|
+
streaming_mode TEXT NOT NULL CHECK (streaming_mode IN ('native','none')),
|
|
28
|
+
tool_bridge TEXT NOT NULL CHECK (tool_bridge IN ('structured_output','none')),
|
|
29
|
+
max_concurrency INTEGER NOT NULL DEFAULT 1 CHECK (max_concurrency > 0),
|
|
30
|
+
max_queue INTEGER NOT NULL DEFAULT 8 CHECK (max_queue >= 0),
|
|
31
|
+
queue_timeout_ms INTEGER NOT NULL DEFAULT 300000 CHECK (queue_timeout_ms > 0),
|
|
32
|
+
run_timeout_ms INTEGER CHECK (run_timeout_ms IS NULL OR run_timeout_ms > 0),
|
|
33
|
+
fixed_workspace TEXT,
|
|
34
|
+
capability_version TEXT,
|
|
35
|
+
capability_verified_at TEXT,
|
|
36
|
+
capability_json TEXT,
|
|
37
|
+
capability_error TEXT,
|
|
38
|
+
created_at TEXT NOT NULL,
|
|
39
|
+
updated_at TEXT NOT NULL
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE TABLE clients (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
name TEXT NOT NULL UNIQUE,
|
|
45
|
+
status TEXT NOT NULL CHECK (status IN ('active','disabled')),
|
|
46
|
+
created_at TEXT NOT NULL,
|
|
47
|
+
updated_at TEXT NOT NULL
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE TABLE credentials (
|
|
51
|
+
id TEXT PRIMARY KEY,
|
|
52
|
+
key_id TEXT NOT NULL UNIQUE,
|
|
53
|
+
client_id TEXT NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
54
|
+
name TEXT NOT NULL,
|
|
55
|
+
prefix TEXT NOT NULL,
|
|
56
|
+
digest TEXT NOT NULL,
|
|
57
|
+
ciphertext TEXT NOT NULL,
|
|
58
|
+
nonce TEXT NOT NULL,
|
|
59
|
+
auth_tag TEXT NOT NULL,
|
|
60
|
+
created_at TEXT NOT NULL,
|
|
61
|
+
expires_at TEXT,
|
|
62
|
+
revoked_at TEXT,
|
|
63
|
+
last_used_at TEXT,
|
|
64
|
+
rotated_from TEXT REFERENCES credentials(id)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE grants (
|
|
68
|
+
client_id TEXT NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
69
|
+
extension_id TEXT NOT NULL REFERENCES extensions(id) ON DELETE CASCADE,
|
|
70
|
+
target_id TEXT NOT NULL REFERENCES invocation_targets(id) ON DELETE CASCADE,
|
|
71
|
+
created_at TEXT NOT NULL,
|
|
72
|
+
PRIMARY KEY (client_id, extension_id, target_id)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
CREATE TABLE runs (
|
|
76
|
+
id TEXT PRIMARY KEY,
|
|
77
|
+
client_id TEXT REFERENCES clients(id),
|
|
78
|
+
extension_id TEXT NOT NULL,
|
|
79
|
+
target_id TEXT NOT NULL,
|
|
80
|
+
endpoint TEXT NOT NULL,
|
|
81
|
+
status TEXT NOT NULL,
|
|
82
|
+
response_id TEXT,
|
|
83
|
+
native_session_id TEXT,
|
|
84
|
+
error_code TEXT,
|
|
85
|
+
queued_at TEXT NOT NULL,
|
|
86
|
+
started_at TEXT,
|
|
87
|
+
completed_at TEXT,
|
|
88
|
+
latency_ms INTEGER
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
CREATE TABLE response_sessions (
|
|
92
|
+
response_id TEXT PRIMARY KEY,
|
|
93
|
+
chain_id TEXT NOT NULL,
|
|
94
|
+
target_id TEXT NOT NULL,
|
|
95
|
+
native_session_id TEXT,
|
|
96
|
+
parent_response_id TEXT REFERENCES response_sessions(response_id),
|
|
97
|
+
child_response_id TEXT REFERENCES response_sessions(response_id),
|
|
98
|
+
workspace_path TEXT,
|
|
99
|
+
stored INTEGER NOT NULL,
|
|
100
|
+
state TEXT NOT NULL,
|
|
101
|
+
expires_at TEXT NOT NULL,
|
|
102
|
+
created_at TEXT NOT NULL,
|
|
103
|
+
updated_at TEXT NOT NULL
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE idempotency_keys (
|
|
107
|
+
client_id TEXT NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
108
|
+
key_digest TEXT NOT NULL,
|
|
109
|
+
request_hash TEXT NOT NULL,
|
|
110
|
+
run_id TEXT NOT NULL REFERENCES runs(id),
|
|
111
|
+
response_id TEXT,
|
|
112
|
+
status TEXT NOT NULL,
|
|
113
|
+
expires_at TEXT NOT NULL,
|
|
114
|
+
PRIMARY KEY (client_id, key_digest)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
CREATE TABLE admin_sessions (
|
|
118
|
+
token_hash TEXT PRIMARY KEY,
|
|
119
|
+
csrf_hash TEXT NOT NULL,
|
|
120
|
+
expires_at TEXT NOT NULL,
|
|
121
|
+
created_at TEXT NOT NULL
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
CREATE INDEX idx_credentials_client ON credentials(client_id);
|
|
125
|
+
CREATE INDEX idx_runs_created ON runs(queued_at DESC);
|
|
126
|
+
CREATE INDEX idx_response_expiry ON response_sessions(expires_at);
|
|
127
|
+
CREATE INDEX idx_response_chain ON response_sessions(chain_id);
|
|
128
|
+
CREATE INDEX idx_idempotency_expiry ON idempotency_keys(expires_at);
|
|
129
|
+
|
|
130
|
+
INSERT INTO core_connection(singleton, base_url) VALUES (1, 'http://127.0.0.1:28771');
|
|
131
|
+
`;
|
|
132
|
+
export const TARGET_VERSION = 1;
|
|
133
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/control-plane/migrations.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkI3B,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC"}
|