@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.
Files changed (149) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +200 -0
  3. package/SECURITY.md +44 -0
  4. package/dist/bin/agent-squad-gateway.js +55 -0
  5. package/dist/bin/agent-squad-gateway.js.map +1 -0
  6. package/dist/config/config.js +124 -0
  7. package/dist/config/config.js.map +1 -0
  8. package/dist/config/paths.js +16 -0
  9. package/dist/config/paths.js.map +1 -0
  10. package/dist/control-plane/clients.js +77 -0
  11. package/dist/control-plane/clients.js.map +1 -0
  12. package/dist/control-plane/core-connection.js +54 -0
  13. package/dist/control-plane/core-connection.js.map +1 -0
  14. package/dist/control-plane/credentials.js +134 -0
  15. package/dist/control-plane/credentials.js.map +1 -0
  16. package/dist/control-plane/db.js +33 -0
  17. package/dist/control-plane/db.js.map +1 -0
  18. package/dist/control-plane/extensions.js +59 -0
  19. package/dist/control-plane/extensions.js.map +1 -0
  20. package/dist/control-plane/grants.js +107 -0
  21. package/dist/control-plane/grants.js.map +1 -0
  22. package/dist/control-plane/idempotency.js +132 -0
  23. package/dist/control-plane/idempotency.js.map +1 -0
  24. package/dist/control-plane/migrations.js +133 -0
  25. package/dist/control-plane/migrations.js.map +1 -0
  26. package/dist/control-plane/response-sessions.js +359 -0
  27. package/dist/control-plane/response-sessions.js.map +1 -0
  28. package/dist/control-plane/retention.js +138 -0
  29. package/dist/control-plane/retention.js.map +1 -0
  30. package/dist/control-plane/runs.js +156 -0
  31. package/dist/control-plane/runs.js.map +1 -0
  32. package/dist/control-plane/targets.js +449 -0
  33. package/dist/control-plane/targets.js.map +1 -0
  34. package/dist/control-plane/types.js +2 -0
  35. package/dist/control-plane/types.js.map +1 -0
  36. package/dist/core-client/client.js +275 -0
  37. package/dist/core-client/client.js.map +1 -0
  38. package/dist/core-client/event-proxy.js +105 -0
  39. package/dist/core-client/event-proxy.js.map +1 -0
  40. package/dist/core-client/types.js +2 -0
  41. package/dist/core-client/types.js.map +1 -0
  42. package/dist/extensions/contract.js +2 -0
  43. package/dist/extensions/contract.js.map +1 -0
  44. package/dist/extensions/openai/auth.js +16 -0
  45. package/dist/extensions/openai/auth.js.map +1 -0
  46. package/dist/extensions/openai/chat.js +403 -0
  47. package/dist/extensions/openai/chat.js.map +1 -0
  48. package/dist/extensions/openai/errors.js +79 -0
  49. package/dist/extensions/openai/errors.js.map +1 -0
  50. package/dist/extensions/openai/image-input.js +79 -0
  51. package/dist/extensions/openai/image-input.js.map +1 -0
  52. package/dist/extensions/openai/models.js +40 -0
  53. package/dist/extensions/openai/models.js.map +1 -0
  54. package/dist/extensions/openai/responses.js +731 -0
  55. package/dist/extensions/openai/responses.js.map +1 -0
  56. package/dist/extensions/openai/routes.js +81 -0
  57. package/dist/extensions/openai/routes.js.map +1 -0
  58. package/dist/extensions/openai/run-attempt.js +96 -0
  59. package/dist/extensions/openai/run-attempt.js.map +1 -0
  60. package/dist/extensions/openai/schemas.js +176 -0
  61. package/dist/extensions/openai/schemas.js.map +1 -0
  62. package/dist/extensions/openai/sse.js +421 -0
  63. package/dist/extensions/openai/sse.js.map +1 -0
  64. package/dist/extensions/openai/tools.js +727 -0
  65. package/dist/extensions/openai/tools.js.map +1 -0
  66. package/dist/extensions/registry.js +30 -0
  67. package/dist/extensions/registry.js.map +1 -0
  68. package/dist/lifecycle/daemon.js +437 -0
  69. package/dist/lifecycle/daemon.js.map +1 -0
  70. package/dist/lifecycle/pidfile.js +34 -0
  71. package/dist/lifecycle/pidfile.js.map +1 -0
  72. package/dist/provider-runtime/antigravity/adapter.js +398 -0
  73. package/dist/provider-runtime/antigravity/adapter.js.map +1 -0
  74. package/dist/provider-runtime/antigravity/conversation.js +31 -0
  75. package/dist/provider-runtime/antigravity/conversation.js.map +1 -0
  76. package/dist/provider-runtime/capability-service.js +191 -0
  77. package/dist/provider-runtime/capability-service.js.map +1 -0
  78. package/dist/provider-runtime/claude/adapter.js +397 -0
  79. package/dist/provider-runtime/claude/adapter.js.map +1 -0
  80. package/dist/provider-runtime/claude/protocol.js +143 -0
  81. package/dist/provider-runtime/claude/protocol.js.map +1 -0
  82. package/dist/provider-runtime/codex/adapter.js +440 -0
  83. package/dist/provider-runtime/codex/adapter.js.map +1 -0
  84. package/dist/provider-runtime/codex/json-rpc-client.js +182 -0
  85. package/dist/provider-runtime/codex/json-rpc-client.js.map +1 -0
  86. package/dist/provider-runtime/codex/protocol.js +128 -0
  87. package/dist/provider-runtime/codex/protocol.js.map +1 -0
  88. package/dist/provider-runtime/command-discovery.js +106 -0
  89. package/dist/provider-runtime/command-discovery.js.map +1 -0
  90. package/dist/provider-runtime/cursor/adapter.js +427 -0
  91. package/dist/provider-runtime/cursor/adapter.js.map +1 -0
  92. package/dist/provider-runtime/cursor/protocol.js +81 -0
  93. package/dist/provider-runtime/cursor/protocol.js.map +1 -0
  94. package/dist/provider-runtime/fake/adapter.js +138 -0
  95. package/dist/provider-runtime/fake/adapter.js.map +1 -0
  96. package/dist/provider-runtime/image-assets.js +409 -0
  97. package/dist/provider-runtime/image-assets.js.map +1 -0
  98. package/dist/provider-runtime/image-support.js +5 -0
  99. package/dist/provider-runtime/image-support.js.map +1 -0
  100. package/dist/provider-runtime/invocation-service.js +503 -0
  101. package/dist/provider-runtime/invocation-service.js.map +1 -0
  102. package/dist/provider-runtime/kimi/adapter.js +657 -0
  103. package/dist/provider-runtime/kimi/adapter.js.map +1 -0
  104. package/dist/provider-runtime/opencode/adapter.js +610 -0
  105. package/dist/provider-runtime/opencode/adapter.js.map +1 -0
  106. package/dist/provider-runtime/opencode/server.js +46 -0
  107. package/dist/provider-runtime/opencode/server.js.map +1 -0
  108. package/dist/provider-runtime/process/line-reader.js +49 -0
  109. package/dist/provider-runtime/process/line-reader.js.map +1 -0
  110. package/dist/provider-runtime/process/managed-process.js +151 -0
  111. package/dist/provider-runtime/process/managed-process.js.map +1 -0
  112. package/dist/provider-runtime/provider-input.js +77 -0
  113. package/dist/provider-runtime/provider-input.js.map +1 -0
  114. package/dist/provider-runtime/register-providers.js +26 -0
  115. package/dist/provider-runtime/register-providers.js.map +1 -0
  116. package/dist/provider-runtime/registry.js +35 -0
  117. package/dist/provider-runtime/registry.js.map +1 -0
  118. package/dist/provider-runtime/replay-buffer.js +168 -0
  119. package/dist/provider-runtime/replay-buffer.js.map +1 -0
  120. package/dist/provider-runtime/scheduler.js +155 -0
  121. package/dist/provider-runtime/scheduler.js.map +1 -0
  122. package/dist/provider-runtime/types.js +2 -0
  123. package/dist/provider-runtime/types.js.map +1 -0
  124. package/dist/provider-runtime/workspaces.js +814 -0
  125. package/dist/provider-runtime/workspaces.js.map +1 -0
  126. package/dist/security/admin-auth.js +109 -0
  127. package/dist/security/admin-auth.js.map +1 -0
  128. package/dist/security/crypto.js +52 -0
  129. package/dist/security/crypto.js.map +1 -0
  130. package/dist/security/secret-files.js +33 -0
  131. package/dist/security/secret-files.js.map +1 -0
  132. package/dist/server/admin-routes.js +610 -0
  133. package/dist/server/admin-routes.js.map +1 -0
  134. package/dist/server/app.js +178 -0
  135. package/dist/server/app.js.map +1 -0
  136. package/dist/server/auth-hooks.js +43 -0
  137. package/dist/server/auth-hooks.js.map +1 -0
  138. package/dist/server/core-routes.js +127 -0
  139. package/dist/server/core-routes.js.map +1 -0
  140. package/dist/server/errors.js +19 -0
  141. package/dist/server/errors.js.map +1 -0
  142. package/dist/server/ui-routes.js +77 -0
  143. package/dist/server/ui-routes.js.map +1 -0
  144. package/dist/web/assets/index-BhqNoPpE.js +206 -0
  145. package/dist/web/assets/index-BhqNoPpE.js.map +1 -0
  146. package/dist/web/assets/index-C5nnaBfZ.css +1 -0
  147. package/dist/web/index.html +14 -0
  148. package/package.json +74 -0
  149. 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":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGpG,MAAM,OAAO,GAAG,4CAA4C,CAAC;AAgC7D,SAAS,kBAAkB,CAAC,GAAkB;IAC5C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,UAAU,EAAE,GAAG,CAAC,YAAY;QAC5B,WAAW,EAAE,GAAG,CAAC,YAAY;KAC9B,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,iBAAiB;IACC;IAAgC;IAA7D,YAA6B,EAAa,EAAmB,SAAiB;QAAjD,OAAE,GAAF,EAAE,CAAW;QAAmB,cAAS,GAAT,SAAS,CAAQ;IAAG,CAAC;IAElF,MAAM,CAAC,QAAgB,EAAE,IAAY,EAAE,YAA2B,IAAI;QACpE,OAAO,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,CAAC,QAAiB;QACpB,MAAM,IAAI,GAAG,QAAQ,KAAK,SAAS;YACjC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAoB;;;;SAIlC,CAAC,CAAC,GAAG,EAAE;YACV,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAA0B;;;;;SAKxC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAErB,OAAO,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,YAAY,CAAC,MAAc;QACzB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEzB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAA0B;;;;;;KAM3D,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACd,IAAI,CAAC,UAAU,IAAI,CAAC,uBAAuB,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpF,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,aAAa,KAAK,QAAQ;eACpC,UAAU,CAAC,UAAU,KAAK,IAAI;eAC9B,CAAC,UAAU,CAAC,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,UAAU,IAAI,GAAG,CAAC,EAAE,CAAC;YACtE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;KAO9B,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAChC,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEtC,OAAO;YACL,YAAY,EAAE,UAAU,CAAC,EAAE;YAC3B,QAAQ,EAAE,UAAU,CAAC,SAAS;YAC9B,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAA0B;;KAE3D,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACX,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAEzD,OAAO,YAAY,CAAC;YAClB,UAAU,EAAE,UAAU,CAAC,UAAU;YACjC,KAAK,EAAE,UAAU,CAAC,KAAK;YACvB,OAAO,EAAE,UAAU,CAAC,QAAQ;SAC7B,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACrB,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;KAE9B,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,CAAC,EAAU,EAAE,IAAY,EAAE,YAA2B,IAAI;QAC9D,MAAM,iBAAiB,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC9B,uEAAuE,CACxE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACV,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;YAEvD,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;YACnF,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;OAE/B,CAAC,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;YACrC,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;YAEnE,OAAO,WAAW,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IAEO,gBAAgB,CACtB,QAAgB,EAChB,IAAY,EACZ,WAA0B,EAC1B,SAAwB;QAExB,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,SAAS,KAAK,IAAI,MAAM,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACtD,MAAM,OAAO,GAAsB;YACjC,EAAE,EAAE,cAAc,UAAU,EAAE,EAAE;YAChC,QAAQ;YACR,MAAM,EAAE,SAAS,KAAK,EAAE;YACxB,MAAM;SACP,CAAC;QAEF,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;KAKf,CAAC,CAAC,GAAG,CACJ,OAAO,CAAC,EAAE,EACV,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,OAAO,CAAC,MAAM,EACd,MAAM,CAAC,MAAM,CAAC,EACd,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,OAAO,EAChB,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,SAAS,EACT,WAAW,CACZ,CAAC;QAEF,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
@@ -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"}