@velum-labs/routekit-daemon 1.4.0 → 1.5.1

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 (172) hide show
  1. package/dist/account-enroll-service.d.ts +1 -3
  2. package/dist/account-enroll-service.js +262 -262
  3. package/dist/account-mutation-service.d.ts +1 -3
  4. package/dist/account-mutation-service.js +302 -283
  5. package/dist/account-query-service.d.ts +1 -3
  6. package/dist/account-query-service.js +88 -86
  7. package/dist/call-attribution-store.js +544 -1
  8. package/dist/control-dispatch.js +9 -15
  9. package/dist/daemon-bootstrap-preflight.js +1 -1
  10. package/dist/daemon-control-handlers.d.ts +1 -1
  11. package/dist/daemon-control-handlers.js +24 -27
  12. package/dist/daemon-generations.d.ts +14 -28
  13. package/dist/daemon-generations.js +114 -53
  14. package/dist/daemon-lifecycle-service.d.ts +1 -3
  15. package/dist/daemon-lifecycle-service.js +88 -94
  16. package/dist/daemon-maintenance.d.ts +27 -49
  17. package/dist/daemon-maintenance.js +5 -1
  18. package/dist/daemon-options.d.ts +3 -2
  19. package/dist/daemon-policy-context.d.ts +1 -0
  20. package/dist/daemon-policy-context.js +1 -0
  21. package/dist/daemon-runtime-state.d.ts +1 -1
  22. package/dist/daemon-runtime-state.js +2 -3
  23. package/dist/daemon-state-context.d.ts +1 -1
  24. package/dist/daemon-state-context.js +1 -38
  25. package/dist/doctor-service.d.ts +1 -3
  26. package/dist/doctor-service.js +87 -91
  27. package/dist/effect/daemon-live.d.ts +7 -6
  28. package/dist/effect/daemon-live.js +57 -135
  29. package/dist/eval-routing-service.d.ts +1 -3
  30. package/dist/eval-routing-service.js +45 -69
  31. package/dist/eval-session-execution-store.d.ts +24 -0
  32. package/dist/eval-session-execution-store.js +253 -0
  33. package/dist/host-protocol.d.ts +1 -0
  34. package/dist/host.js +3 -0
  35. package/dist/internal-providers/provider-anthropic/index.d.ts +23 -0
  36. package/dist/internal-providers/provider-anthropic/index.js +42 -0
  37. package/dist/internal-providers/provider-anthropic/test/provider.test.js +33 -0
  38. package/dist/internal-providers/provider-azure/index.d.ts +25 -0
  39. package/dist/internal-providers/provider-azure/index.js +101 -0
  40. package/dist/internal-providers/provider-azure/test/provider.test.js +83 -0
  41. package/dist/internal-providers/provider-bedrock/codec.d.ts +11 -0
  42. package/dist/internal-providers/provider-bedrock/codec.js +484 -0
  43. package/dist/internal-providers/provider-bedrock/connection-settings.d.ts +6 -0
  44. package/dist/internal-providers/provider-bedrock/connection-settings.js +41 -0
  45. package/dist/internal-providers/provider-bedrock/connection.d.ts +17 -0
  46. package/dist/internal-providers/provider-bedrock/connection.js +143 -0
  47. package/dist/internal-providers/provider-bedrock/index.d.ts +27 -0
  48. package/dist/internal-providers/provider-bedrock/index.js +47 -0
  49. package/dist/internal-providers/provider-bedrock/source.d.ts +39 -0
  50. package/dist/internal-providers/provider-bedrock/source.js +454 -0
  51. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.d.ts +1 -0
  52. package/dist/internal-providers/provider-bedrock/test/bedrock-source.test.js +786 -0
  53. package/dist/internal-providers/provider-bedrock/test/connection.test.d.ts +1 -0
  54. package/dist/internal-providers/provider-bedrock/test/connection.test.js +249 -0
  55. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.d.ts +1 -0
  56. package/dist/internal-providers/provider-bedrock/test/workload-credentials.test.js +29 -0
  57. package/dist/internal-providers/provider-bedrock/workload-credentials.d.ts +5 -0
  58. package/dist/internal-providers/provider-bedrock/workload-credentials.js +14 -0
  59. package/dist/internal-providers/provider-claude-code/index.d.ts +6 -0
  60. package/dist/internal-providers/provider-claude-code/index.js +4 -0
  61. package/dist/internal-providers/provider-claude-code/module-api.d.ts +25 -0
  62. package/dist/internal-providers/provider-claude-code/module-api.js +31 -0
  63. package/dist/internal-providers/provider-claude-code/relay.d.ts +20 -0
  64. package/dist/internal-providers/provider-claude-code/relay.js +115 -0
  65. package/dist/internal-providers/provider-claude-code/subscription.d.ts +2 -0
  66. package/dist/internal-providers/provider-claude-code/subscription.js +194 -0
  67. package/dist/internal-providers/provider-claude-code/test/http-submission.test.d.ts +1 -0
  68. package/dist/internal-providers/provider-claude-code/test/http-submission.test.js +140 -0
  69. package/dist/internal-providers/provider-claude-code/test/relay.test.d.ts +1 -0
  70. package/dist/internal-providers/provider-claude-code/test/relay.test.js +379 -0
  71. package/dist/internal-providers/provider-claude-code/test/subscription.test.d.ts +1 -0
  72. package/dist/internal-providers/provider-claude-code/test/subscription.test.js +256 -0
  73. package/dist/internal-providers/provider-cliproxy/index.d.ts +9 -0
  74. package/dist/internal-providers/provider-cliproxy/index.js +15 -0
  75. package/dist/internal-providers/provider-codex/index.d.ts +6 -0
  76. package/dist/internal-providers/provider-codex/index.js +7 -0
  77. package/dist/internal-providers/provider-codex/module-api.d.ts +25 -0
  78. package/dist/internal-providers/provider-codex/module-api.js +31 -0
  79. package/dist/internal-providers/provider-codex/relay.d.ts +123 -0
  80. package/dist/internal-providers/provider-codex/relay.js +261 -0
  81. package/dist/internal-providers/provider-codex/subscription.d.ts +2 -0
  82. package/dist/internal-providers/provider-codex/subscription.js +555 -0
  83. package/dist/internal-providers/provider-codex/test/http-submission.test.d.ts +1 -0
  84. package/dist/internal-providers/provider-codex/test/http-submission.test.js +142 -0
  85. package/dist/internal-providers/provider-codex/test/relay.test.d.ts +1 -0
  86. package/dist/internal-providers/provider-codex/test/relay.test.js +600 -0
  87. package/dist/internal-providers/provider-codex/test/subscription.test.d.ts +1 -0
  88. package/dist/internal-providers/provider-codex/test/subscription.test.js +70 -0
  89. package/dist/internal-providers/provider-codex/test/translated-content.test.d.ts +1 -0
  90. package/dist/internal-providers/provider-codex/test/translated-content.test.js +91 -0
  91. package/dist/internal-providers/provider-custom/index.d.ts +9 -0
  92. package/dist/internal-providers/provider-custom/index.js +15 -0
  93. package/dist/internal-providers/provider-google/index.d.ts +23 -0
  94. package/dist/internal-providers/provider-google/index.js +43 -0
  95. package/dist/internal-providers/provider-google/test/provider.test.d.ts +1 -0
  96. package/dist/internal-providers/provider-google/test/provider.test.js +34 -0
  97. package/dist/internal-providers/provider-google-vertex/index.d.ts +28 -0
  98. package/dist/internal-providers/provider-google-vertex/index.js +137 -0
  99. package/dist/internal-providers/provider-google-vertex/test/provider.test.d.ts +1 -0
  100. package/dist/internal-providers/provider-google-vertex/test/provider.test.js +85 -0
  101. package/dist/internal-providers/provider-openai/documented-reasoning.d.ts +9 -0
  102. package/dist/internal-providers/provider-openai/documented-reasoning.js +26 -0
  103. package/dist/internal-providers/provider-openai/index.d.ts +23 -0
  104. package/dist/internal-providers/provider-openai/index.js +74 -0
  105. package/dist/internal-providers/provider-openai/test/provider.test.d.ts +1 -0
  106. package/dist/internal-providers/provider-openai/test/provider.test.js +268 -0
  107. package/dist/internal-providers/provider-openai-compatible/index.d.ts +9 -0
  108. package/dist/internal-providers/provider-openai-compatible/index.js +15 -0
  109. package/dist/internal-providers/provider-openai-compatible/test/provider.test.d.ts +1 -0
  110. package/dist/internal-providers/provider-openai-compatible/test/provider.test.js +40 -0
  111. package/dist/internal-providers/provider-openrouter/connection.d.ts +5 -0
  112. package/dist/internal-providers/provider-openrouter/connection.js +143 -0
  113. package/dist/internal-providers/provider-openrouter/index.d.ts +28 -0
  114. package/dist/internal-providers/provider-openrouter/index.js +42 -0
  115. package/dist/internal-providers/provider-openrouter/pricing.d.ts +4 -0
  116. package/dist/internal-providers/provider-openrouter/pricing.js +108 -0
  117. package/dist/internal-providers/provider-openrouter/test/connection.test.d.ts +1 -0
  118. package/dist/internal-providers/provider-openrouter/test/connection.test.js +71 -0
  119. package/dist/internal-providers/provider-openrouter/test/pricing.test.d.ts +1 -0
  120. package/dist/internal-providers/provider-openrouter/test/pricing.test.js +45 -0
  121. package/dist/internal-providers/provider-openrouter/test/provider.test.d.ts +1 -0
  122. package/dist/internal-providers/provider-openrouter/test/provider.test.js +141 -0
  123. package/dist/internal-providers/provider-openrouter/test/validation.test.d.ts +1 -0
  124. package/dist/internal-providers/provider-openrouter/test/validation.test.js +225 -0
  125. package/dist/launcher-service.d.ts +3 -7
  126. package/dist/launcher-service.js +144 -133
  127. package/dist/provider-modules.d.ts +6 -0
  128. package/dist/provider-modules.js +55 -0
  129. package/dist/provider-query-service.d.ts +1 -3
  130. package/dist/provider-query-service.js +140 -143
  131. package/dist/router-generation-service.d.ts +1 -3
  132. package/dist/router-generation-service.js +83 -78
  133. package/dist/services/eval-session/service.d.ts +20 -8
  134. package/dist/services/eval-session/service.js +410 -40
  135. package/dist/services/gateway-generation/service.d.ts +7 -3
  136. package/dist/services/gateway-generation/service.js +126 -63
  137. package/dist/services/generations/service.d.ts +3 -3
  138. package/dist/services/generations/service.js +5 -1
  139. package/dist/telemetry-application-service.d.ts +1 -3
  140. package/dist/telemetry-application-service.js +41 -45
  141. package/dist/test/call-attribution-store.test.js +186 -0
  142. package/dist/test/daemon-accounts.test.js +2 -2
  143. package/dist/test/daemon-data-plane.test.js +24 -45
  144. package/dist/test/daemon-fixtures.js +1 -1
  145. package/dist/test/daemon-generation-transaction.test.js +66 -2
  146. package/dist/test/daemon-runtime-state.test.js +28 -1
  147. package/dist/test/effect-daemon.test.js +38 -0
  148. package/dist/test/eval-routing-application-service.test.js +6 -6
  149. package/dist/test/eval-session-execution.test.d.ts +1 -0
  150. package/dist/test/eval-session-execution.test.js +356 -0
  151. package/dist/test/eval-session-service.test.js +70 -1
  152. package/dist/test/host-worker-session.test.js +1 -0
  153. package/dist/test/launcher-session.test.d.ts +1 -0
  154. package/dist/test/launcher-session.test.js +87 -0
  155. package/dist/test/leaderboard.test.js +25 -1
  156. package/dist/test/provider-modules.test.d.ts +1 -0
  157. package/dist/test/provider-modules.test.js +65 -0
  158. package/dist/test/routekit-upstream-sources.test.d.ts +1 -0
  159. package/dist/test/routekit-upstream-sources.test.js +40 -0
  160. package/dist/test/runtime-composition-types.test.js +11 -0
  161. package/dist/token-application-service.d.ts +1 -3
  162. package/dist/token-application-service.js +38 -44
  163. package/dist/worker.js +5 -3
  164. package/package.json +35 -11
  165. package/dist/application-services.d.ts +0 -10
  166. package/dist/application-services.js +0 -22
  167. package/dist/daemon-lifecycle.d.ts +0 -54
  168. package/dist/daemon-lifecycle.js +0 -177
  169. package/dist/test/application-services.test.js +0 -135
  170. package/dist/test/daemon-lifecycle.test.js +0 -182
  171. /package/dist/{test/application-services.test.d.ts → internal-providers/provider-anthropic/test/provider.test.d.ts} +0 -0
  172. /package/dist/{test/daemon-lifecycle.test.d.ts → internal-providers/provider-azure/test/provider.test.d.ts} +0 -0
@@ -1,7 +1,5 @@
1
1
  import type { EffectRouteKitControlHandlers } from "@velum-labs/routekit-control/effect";
2
2
  type AccountEnrollHandlers = Pick<EffectRouteKitControlHandlers, "accounts.enroll" | "accounts.enrollActivate">;
3
3
  /** Owns account enrollment and activation transactions. */
4
- export declare class AccountEnrollService {
5
- handlers(): AccountEnrollHandlers;
6
- }
4
+ export declare const accountEnrollHandlers: AccountEnrollHandlers;
7
5
  export {};
@@ -1,6 +1,7 @@
1
1
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { cliproxyAuthDirectory, defaultSubscriptionAccountDirectory, sanitizeSubscriptionLabel, subscriptionAccountIdentity, subscriptionCredentialFingerprint } from "@velum-labs/routekit-accounts";
3
+ import { cliproxyAuthDirectory, defaultSubscriptionAccountDirectory, sanitizeSubscriptionLabel } from "@velum-labs/routekit-accounts";
4
+ import { AccountAuth } from "@velum-labs/routekit-accounts/effect";
4
5
  import { resolveAccountConnector } from "@velum-labs/routekit-registry";
5
6
  import { ControlError } from "@velum-labs/routekit-runtime/control";
6
7
  import { writeFileAtomic } from "@velum-labs/routekit-runtime/filesystem";
@@ -9,282 +10,281 @@ import { Effect } from "effect";
9
10
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
10
11
  import { cleanupAccountTransaction, markAccountTransactionCommitted, prepareAccountTransaction, rollbackAccountTransaction } from "./account-transaction.js";
11
12
  import { DaemonHost } from "./daemon-host-context.js";
12
- import { daemonAccountServices } from "./account-services.js";
13
+ import { DaemonEnv } from "./daemon-env-context.js";
13
14
  import { parseConfigDocument, safeCliproxyCredentialBlob, safeCliproxyLabel, safeCredentialBlob } from "./daemon-maintenance.js";
15
+ import { DaemonState } from "./daemon-state-context.js";
16
+ import { Generations } from "./services/generations/service.js";
17
+ import { Sidecar } from "./sidecar-context.js";
14
18
  /** Owns account enrollment and activation transactions. */
15
- export class AccountEnrollService {
16
- handlers() {
17
- return {
18
- "accounts.enroll": (params) => Effect.gen(function* () {
19
- const { env: daemonEnv, state: runtimeState, generations } = yield* daemonAccountServices;
20
- const host = yield* DaemonHost;
21
- const env = daemonEnv.env;
22
- return yield* runtimeState.serializeEffect(Effect.gen(function* () {
23
- const path = yield* Effect.try({
24
- try: () => {
25
- const label = sanitizeSubscriptionLabel(params.label);
26
- if (label !== params.label || label.startsWith(".")) {
27
- throw new ControlError({
28
- code: "bad_request",
29
- message: "account label must already be normalized"
30
- });
31
- }
32
- const directory = defaultSubscriptionAccountDirectory(params.kind, env);
33
- mkdirSync(directory, { recursive: true, mode: 0o700 });
34
- const credentialPath = join(directory, `${label}.json`);
35
- if (existsSync(credentialPath)) {
19
+ export const accountEnrollHandlers = {
20
+ "accounts.enroll": (params) => Effect.gen(function* () {
21
+ const daemonEnv = yield* DaemonEnv;
22
+ const runtimeState = yield* DaemonState;
23
+ const generations = yield* Generations;
24
+ return yield* runtimeState.serializeEffect(Effect.gen(function* () {
25
+ const path = yield* Effect.try({
26
+ try: () => {
27
+ const label = sanitizeSubscriptionLabel(params.label);
28
+ if (label !== params.label || label.startsWith(".")) {
29
+ throw new ControlError({
30
+ code: "bad_request",
31
+ message: "account label must already be normalized"
32
+ });
33
+ }
34
+ const directory = defaultSubscriptionAccountDirectory(params.kind, daemonEnv.env);
35
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
36
+ const credentialPath = join(directory, `${label}.json`);
37
+ if (existsSync(credentialPath)) {
38
+ throw new ControlError({
39
+ code: "conflict",
40
+ message: `${params.kind}/${label} is already enrolled; remove it before enrolling again`
41
+ });
42
+ }
43
+ writeFileAtomic(credentialPath, `${JSON.stringify(safeCredentialBlob(params.kind, params.credential), null, 2)}\n`, { mode: 0o600 });
44
+ chmodSync(credentialPath, 0o600);
45
+ return credentialPath;
46
+ },
47
+ catch: toRouteKitFailure
48
+ });
49
+ yield* generations
50
+ .replace(runtimeState.config, runtimeState.document, {
51
+ write: false,
52
+ accountRevision: true
53
+ })
54
+ .pipe(Effect.catch((error) => {
55
+ rmSync(path, { force: true });
56
+ return Effect.fail(routeKitError(error));
57
+ }));
58
+ return { enrolled: true, revision: runtimeState.revisions.accounts };
59
+ }));
60
+ }),
61
+ "accounts.enrollActivate": (params) => Effect.gen(function* () {
62
+ const daemonEnv = yield* DaemonEnv;
63
+ const runtimeState = yield* DaemonState;
64
+ const generations = yield* Generations;
65
+ const authHealth = yield* AccountAuth;
66
+ const sidecar = yield* Sidecar;
67
+ const host = yield* DaemonHost;
68
+ const env = daemonEnv.env;
69
+ const home = daemonEnv.home;
70
+ const configPath = daemonEnv.configPath;
71
+ const replaceRouter = generations.replace;
72
+ const resolved = resolveAccountConnector(params.kind);
73
+ if (resolved === undefined) {
74
+ return yield* Effect.fail(new ControlError({
75
+ code: "bad_request",
76
+ message: `unknown subscription kind: ${params.kind}`
77
+ }));
78
+ }
79
+ const kind = resolved.kind;
80
+ const connector = resolved.info.connector;
81
+ const provider = connector === "cliproxy" ? "cliproxy" : kind;
82
+ const prepared = yield* Effect.try({
83
+ try: () => {
84
+ const seenLabels = new Set();
85
+ return params.accounts.map((account) => {
86
+ const label = connector === "native"
87
+ ? sanitizeSubscriptionLabel(account.label)
88
+ : safeCliproxyLabel(account.label);
89
+ if (label !== account.label || (connector === "native" && label.startsWith("."))) {
90
+ throw new ControlError({
91
+ code: "bad_request",
92
+ message: "account label must already be normalized"
93
+ });
94
+ }
95
+ if (seenLabels.has(label)) {
96
+ throw new ControlError({
97
+ code: "bad_request",
98
+ message: `duplicate account label: ${label}`
99
+ });
100
+ }
101
+ seenLabels.add(label);
102
+ const directory = connector === "native"
103
+ ? defaultSubscriptionAccountDirectory(kind, env)
104
+ : cliproxyAuthDirectory(env);
105
+ const path = join(directory, `${label}.json`);
106
+ let credential = account.credential;
107
+ if (credential === undefined) {
108
+ if (!existsSync(path)) {
109
+ throw new ControlError({
110
+ code: "not_found",
111
+ message: `${kind}/${label} is not enrolled`
112
+ });
113
+ }
114
+ try {
115
+ credential = JSON.parse(readFileSync(path, "utf8"));
116
+ }
117
+ catch {
118
+ throw new ControlError({
119
+ code: "bad_request",
120
+ message: `${kind}/${label} has an invalid stored credential`
121
+ });
122
+ }
123
+ }
124
+ const blob = connector === "native"
125
+ ? safeCredentialBlob(kind, credential)
126
+ : safeCliproxyCredentialBlob(kind, credential);
127
+ const content = `${JSON.stringify(blob, null, 2)}\n`;
128
+ if (connector === "native" &&
129
+ account.credential !== undefined &&
130
+ existsSync(path) &&
131
+ readFileSync(path, "utf8") !== content) {
132
+ throw new ControlError({
133
+ code: "conflict",
134
+ message: `${kind}/${label} is already enrolled with different credentials`
135
+ });
136
+ }
137
+ return {
138
+ label,
139
+ directory,
140
+ path,
141
+ content,
142
+ credentialProvided: account.credential !== undefined
143
+ };
144
+ });
145
+ },
146
+ catch: toRouteKitFailure
147
+ });
148
+ yield* runtimeState.serializeEffect(Effect.gen(function* () {
149
+ yield* Effect.try({
150
+ try: () => {
151
+ for (const entry of prepared) {
152
+ if (!entry.credentialProvided) {
153
+ if (!existsSync(entry.path)) {
36
154
  throw new ControlError({
37
- code: "conflict",
38
- message: `${params.kind}/${label} is already enrolled; remove it before enrolling again`
155
+ code: "not_found",
156
+ message: `${kind}/${entry.label} is not enrolled`
39
157
  });
40
158
  }
41
- writeFileAtomic(credentialPath, `${JSON.stringify(safeCredentialBlob(params.kind, params.credential), null, 2)}\n`, { mode: 0o600 });
42
- chmodSync(credentialPath, 0o600);
43
- return credentialPath;
44
- },
45
- catch: toRouteKitFailure
46
- });
47
- yield* generations
48
- .replace(runtimeState.config, runtimeState.document, {
49
- write: false,
50
- accountRevision: true
51
- })
52
- .pipe(Effect.catch((error) => {
53
- rmSync(path, { force: true });
54
- return Effect.fail(routeKitError(error));
55
- }));
56
- return { enrolled: true, revision: runtimeState.revisions.accounts };
57
- }));
58
- }),
59
- "accounts.enrollActivate": (params) => Effect.gen(function* () {
60
- const { env: daemonEnv, state: runtimeState, generations, auth: authHealth, sidecar } = yield* daemonAccountServices;
61
- const host = yield* DaemonHost;
62
- const env = daemonEnv.env;
63
- const home = daemonEnv.home;
64
- const configPath = daemonEnv.configPath;
65
- const replaceRouter = generations.replace;
66
- const resolved = resolveAccountConnector(params.kind);
67
- if (resolved === undefined) {
68
- return yield* Effect.fail(new ControlError({
69
- code: "bad_request",
70
- message: `unknown subscription kind: ${params.kind}`
71
- }));
72
- }
73
- const kind = resolved.kind;
74
- const connector = resolved.info.connector;
75
- const provider = connector === "cliproxy" ? "cliproxy" : kind;
76
- const prepared = yield* Effect.try({
77
- try: () => {
78
- const seenLabels = new Set();
79
- return params.accounts.map((account) => {
80
- const label = connector === "native"
81
- ? sanitizeSubscriptionLabel(account.label)
82
- : safeCliproxyLabel(account.label);
83
- if (label !== account.label || (connector === "native" && label.startsWith("."))) {
84
- throw new ControlError({
85
- code: "bad_request",
86
- message: "account label must already be normalized"
87
- });
159
+ let stored;
160
+ try {
161
+ stored = JSON.parse(readFileSync(entry.path, "utf8"));
88
162
  }
89
- if (seenLabels.has(label)) {
163
+ catch {
90
164
  throw new ControlError({
91
165
  code: "bad_request",
92
- message: `duplicate account label: ${label}`
166
+ message: `${kind}/${entry.label} has an invalid stored credential`
93
167
  });
94
168
  }
95
- seenLabels.add(label);
96
- const directory = connector === "native"
97
- ? defaultSubscriptionAccountDirectory(kind, env)
98
- : cliproxyAuthDirectory(env);
99
- const path = join(directory, `${label}.json`);
100
- let credential = account.credential;
101
- if (credential === undefined) {
102
- if (!existsSync(path)) {
103
- throw new ControlError({
104
- code: "not_found",
105
- message: `${kind}/${label} is not enrolled`
106
- });
107
- }
108
- try {
109
- credential = JSON.parse(readFileSync(path, "utf8"));
110
- }
111
- catch {
112
- throw new ControlError({
113
- code: "bad_request",
114
- message: `${kind}/${label} has an invalid stored credential`
115
- });
116
- }
117
- }
118
169
  const blob = connector === "native"
119
- ? safeCredentialBlob(kind, credential)
120
- : safeCliproxyCredentialBlob(kind, credential);
121
- const content = `${JSON.stringify(blob, null, 2)}\n`;
122
- if (connector === "native" &&
123
- account.credential !== undefined &&
124
- existsSync(path) &&
125
- readFileSync(path, "utf8") !== content) {
126
- throw new ControlError({
127
- code: "conflict",
128
- message: `${kind}/${label} is already enrolled with different credentials`
129
- });
130
- }
131
- return {
132
- label,
133
- directory,
134
- path,
135
- content,
136
- credentialProvided: account.credential !== undefined
137
- };
138
- });
170
+ ? safeCredentialBlob(kind, stored)
171
+ : safeCliproxyCredentialBlob(kind, stored);
172
+ entry.content = `${JSON.stringify(blob, null, 2)}\n`;
173
+ }
174
+ else if (connector === "native" &&
175
+ existsSync(entry.path) &&
176
+ readFileSync(entry.path, "utf8") !== entry.content) {
177
+ throw new ControlError({
178
+ code: "conflict",
179
+ message: `${kind}/${entry.label} is already enrolled with different credentials`
180
+ });
181
+ }
182
+ }
183
+ },
184
+ catch: toRouteKitFailure
185
+ });
186
+ const unchanged = prepared.every((entry) => existsSync(entry.path) && readFileSync(entry.path, "utf8") === entry.content);
187
+ if (unchanged && runtimeState.config.sources[provider] !== undefined) {
188
+ return;
189
+ }
190
+ const raw = parseYaml(runtimeState.document);
191
+ const sources = typeof raw.sources === "object" && raw.sources !== null && !Array.isArray(raw.sources)
192
+ ? { ...raw.sources }
193
+ : Object.fromEntries(Object.entries(runtimeState.config.sources).map(([sourceId, source]) => [
194
+ sourceId,
195
+ source
196
+ ]));
197
+ sources[provider] ??= { use: provider };
198
+ raw.sources = sources;
199
+ delete raw.providers;
200
+ const document = stringifyYaml(raw);
201
+ const nextConfig = parseConfigDocument(document);
202
+ const transaction = prepareAccountTransaction({
203
+ home,
204
+ configPath,
205
+ accountPaths: [
206
+ ...prepared.map((entry) => entry.path),
207
+ ...(connector === "native"
208
+ ? [join(home, "subscriptions", "account-auth.v1.json")]
209
+ : [])
210
+ ],
211
+ accountRoots: [
212
+ ...new Set(prepared.map((entry) => entry.directory)),
213
+ ...(connector === "native" ? [join(home, "subscriptions")] : [])
214
+ ],
215
+ kind,
216
+ provider,
217
+ labels: prepared.map((entry) => entry.label)
218
+ });
219
+ host.onAccountTransactionPhase?.("prepared");
220
+ let routerReplaced = false;
221
+ const previousConfig = runtimeState.config;
222
+ const previousDocument = runtimeState.document;
223
+ yield* Effect.gen(function* () {
224
+ yield* Effect.try({
225
+ try: () => {
226
+ for (const entry of prepared) {
227
+ mkdirSync(entry.directory, { recursive: true, mode: 0o700 });
228
+ writeFileAtomic(entry.path, entry.content, { mode: 0o600 });
229
+ chmodSync(entry.path, 0o600);
230
+ }
231
+ host.onAccountTransactionPhase?.("credentials-written");
139
232
  },
140
233
  catch: toRouteKitFailure
141
234
  });
142
- yield* runtimeState.serializeEffect(Effect.gen(function* () {
143
- yield* Effect.try({
144
- try: () => {
145
- for (const entry of prepared) {
146
- if (!entry.credentialProvided) {
147
- if (!existsSync(entry.path)) {
148
- throw new ControlError({
149
- code: "not_found",
150
- message: `${kind}/${entry.label} is not enrolled`
151
- });
152
- }
153
- let stored;
154
- try {
155
- stored = JSON.parse(readFileSync(entry.path, "utf8"));
156
- }
157
- catch {
158
- throw new ControlError({
159
- code: "bad_request",
160
- message: `${kind}/${entry.label} has an invalid stored credential`
161
- });
162
- }
163
- const blob = connector === "native"
164
- ? safeCredentialBlob(kind, stored)
165
- : safeCliproxyCredentialBlob(kind, stored);
166
- entry.content = `${JSON.stringify(blob, null, 2)}\n`;
167
- }
168
- else if (connector === "native" &&
169
- existsSync(entry.path) &&
170
- readFileSync(entry.path, "utf8") !== entry.content) {
171
- throw new ControlError({
172
- code: "conflict",
173
- message: `${kind}/${entry.label} is already enrolled with different credentials`
174
- });
175
- }
176
- }
177
- },
178
- catch: toRouteKitFailure
179
- });
180
- const unchanged = prepared.every((entry) => existsSync(entry.path) && readFileSync(entry.path, "utf8") === entry.content);
181
- if (unchanged &&
182
- runtimeState.config.providers[provider] !== undefined) {
183
- return;
184
- }
185
- const raw = parseYaml(runtimeState.document);
186
- const providers = typeof raw.providers === "object" &&
187
- raw.providers !== null &&
188
- !Array.isArray(raw.providers)
189
- ? { ...raw.providers }
190
- : {};
191
- providers[provider] ??= {};
192
- raw.providers = providers;
193
- const document = stringifyYaml(raw);
194
- const nextConfig = parseConfigDocument(document);
195
- const transaction = prepareAccountTransaction({
196
- home,
197
- configPath,
198
- accountPaths: [
199
- ...prepared.map((entry) => entry.path),
200
- ...(connector === "native"
201
- ? [join(home, "subscriptions", "account-auth.v1.json")]
202
- : [])
203
- ],
204
- accountRoots: [
205
- ...new Set(prepared.map((entry) => entry.directory)),
206
- ...(connector === "native" ? [join(home, "subscriptions")] : [])
207
- ],
208
- kind,
209
- provider,
210
- labels: prepared.map((entry) => entry.label)
211
- });
212
- host.onAccountTransactionPhase?.("prepared");
213
- let routerReplaced = false;
214
- const previousConfig = runtimeState.config;
215
- const previousDocument = runtimeState.document;
216
- yield* Effect.gen(function* () {
217
- yield* Effect.try({
218
- try: () => {
219
- for (const entry of prepared) {
220
- mkdirSync(entry.directory, { recursive: true, mode: 0o700 });
221
- writeFileAtomic(entry.path, entry.content, { mode: 0o600 });
222
- chmodSync(entry.path, 0o600);
223
- }
224
- host.onAccountTransactionPhase?.("credentials-written");
225
- },
226
- catch: toRouteKitFailure
227
- });
228
- yield* replaceRouter(nextConfig, document, {
229
- write: true,
230
- configRevision: true,
231
- accountRevision: true,
232
- persist: () => Effect.gen(function* () {
233
- if (connector === "native") {
234
- for (const entry of prepared) {
235
- yield* authHealth.activateFingerprint(subscriptionAccountIdentity(kind, entry.label), subscriptionCredentialFingerprint(entry.path));
236
- }
237
- }
238
- markAccountTransactionCommitted(transaction);
239
- if (connector === "cliproxy") {
240
- yield* sidecar.refresh;
241
- }
242
- host.onAccountTransactionPhase?.("committed");
243
- })
244
- });
245
- routerReplaced = true;
246
- host.onAccountTransactionPhase?.("router-swapped");
247
- yield* Effect.try({
248
- try: () => cleanupAccountTransaction(transaction),
249
- catch: toRouteKitFailure
250
- }).pipe(Effect.ignore);
251
- }).pipe(Effect.catch((error) => Effect.gen(function* () {
252
- const rollbackFailures = [];
253
- yield* Effect.try({
254
- try: () => {
255
- rollbackAccountTransaction(transaction, home);
256
- },
257
- catch: toRouteKitFailure
258
- }).pipe(Effect.flatMap(() => authHealth.reload), Effect.catch((rollbackError) => {
259
- rollbackFailures.push(rollbackError);
260
- return Effect.void;
261
- }));
235
+ yield* replaceRouter(nextConfig, document, {
236
+ write: true,
237
+ configRevision: true,
238
+ accountRevision: true,
239
+ persist: () => Effect.gen(function* () {
240
+ markAccountTransactionCommitted(transaction);
262
241
  if (connector === "cliproxy") {
263
- yield* sidecar.refresh.pipe(Effect.catch((rollbackError) => {
264
- rollbackFailures.push(rollbackError);
265
- return Effect.void;
266
- }));
267
- }
268
- if (routerReplaced) {
269
- yield* replaceRouter(previousConfig, previousDocument, { write: false }).pipe(Effect.catch((rollbackError) => {
270
- rollbackFailures.push(rollbackError);
271
- return Effect.void;
272
- }));
242
+ yield* sidecar.refresh;
273
243
  }
274
- if (rollbackFailures.length > 0) {
275
- return yield* Effect.fail(new AggregateError([error, ...rollbackFailures], `could not activate ${kind}; rollback failed`));
276
- }
277
- return yield* Effect.fail(routeKitError(error));
278
- })));
244
+ host.onAccountTransactionPhase?.("committed");
245
+ })
246
+ });
247
+ routerReplaced = true;
248
+ host.onAccountTransactionPhase?.("router-swapped");
249
+ yield* Effect.try({
250
+ try: () => cleanupAccountTransaction(transaction),
251
+ catch: toRouteKitFailure
252
+ }).pipe(Effect.ignore);
253
+ }).pipe(Effect.catch((error) => Effect.gen(function* () {
254
+ const rollbackFailures = [];
255
+ yield* Effect.try({
256
+ try: () => {
257
+ rollbackAccountTransaction(transaction, home);
258
+ },
259
+ catch: toRouteKitFailure
260
+ }).pipe(Effect.flatMap(() => authHealth.reload), Effect.catch((rollbackError) => {
261
+ rollbackFailures.push(rollbackError);
262
+ return Effect.void;
279
263
  }));
280
- return {
281
- enrolled: prepared.map((entry) => ({ subscriptionKind: kind, label: entry.label })),
282
- activated: true,
283
- configPath,
284
- configRevision: runtimeState.revisions.config,
285
- accountRevision: runtimeState.revisions.accounts
286
- };
287
- })
264
+ if (connector === "cliproxy") {
265
+ yield* sidecar.refresh.pipe(Effect.catch((rollbackError) => {
266
+ rollbackFailures.push(rollbackError);
267
+ return Effect.void;
268
+ }));
269
+ }
270
+ if (routerReplaced) {
271
+ yield* replaceRouter(previousConfig, previousDocument, { write: false }).pipe(Effect.catch((rollbackError) => {
272
+ rollbackFailures.push(rollbackError);
273
+ return Effect.void;
274
+ }));
275
+ }
276
+ if (rollbackFailures.length > 0) {
277
+ return yield* Effect.fail(new AggregateError([error, ...rollbackFailures], `could not activate ${kind}; rollback failed`));
278
+ }
279
+ return yield* Effect.fail(routeKitError(error));
280
+ })));
281
+ }));
282
+ return {
283
+ enrolled: prepared.map((entry) => ({ subscriptionKind: kind, label: entry.label })),
284
+ activated: true,
285
+ configPath,
286
+ configRevision: runtimeState.revisions.config,
287
+ accountRevision: runtimeState.revisions.accounts
288
288
  };
289
- }
290
- }
289
+ })
290
+ };
@@ -1,7 +1,5 @@
1
1
  import type { EffectRouteKitControlHandlers } from "@velum-labs/routekit-control/effect";
2
2
  type AccountMutationHandlers = Pick<EffectRouteKitControlHandlers, "accounts.remove" | "accounts.rename" | "accounts.sync" | "accounts.resetCredits" | "accounts.redeemReset">;
3
3
  /** Owns account removal, rename, sync, and credit redemption. */
4
- export declare class AccountMutationService {
5
- handlers(): AccountMutationHandlers;
6
- }
4
+ export declare const accountMutationHandlers: AccountMutationHandlers;
7
5
  export {};