@velum-labs/routekit-daemon 1.4.0 → 1.5.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 (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,6 +1,7 @@
1
1
  import { chmodSync, existsSync, lstatSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { cliproxyAccountEntries, cliproxyAccountMatchesKind, defaultSubscriptionAccountDirectory, RateLimitTracker, removeCliproxyAccount, removeSubscriptionAccount, renameSubscriptionAccount, sanitizeSubscriptionLabel, subscriptionAccountIdentity } from "@velum-labs/routekit-accounts";
4
+ import { AccountActivity, 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,8 +10,12 @@ 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 { accountEntries, parseConfigDocument } from "./daemon-maintenance.js";
15
+ import { DaemonState } from "./daemon-state-context.js";
16
+ import { ActiveGateway } from "./services/active-gateway/service.js";
17
+ import { Generations } from "./services/generations/service.js";
18
+ import { Sidecar } from "./sidecar-context.js";
14
19
  function rollbackAccountCoordinators(transaction, home, activity, authHealth, error, message) {
15
20
  return Effect.gen(function* () {
16
21
  yield* Effect.try({
@@ -25,298 +30,312 @@ function rollbackAccountCoordinators(transaction, home, activity, authHealth, er
25
30
  });
26
31
  }
27
32
  /** Owns account removal, rename, sync, and credit redemption. */
28
- export class AccountMutationService {
29
- handlers() {
30
- return {
31
- "accounts.remove": (params) => Effect.gen(function* () {
32
- const { env: daemonEnv, state: runtimeState, generations, activity, auth: authHealth, sidecar } = yield* daemonAccountServices;
33
- const env = daemonEnv.env;
34
- const home = daemonEnv.home;
35
- const configPath = daemonEnv.configPath;
36
- const replaceRouter = generations.replace;
37
- const resolved = resolveAccountConnector(params.kind);
38
- if (resolved === undefined) {
39
- return yield* Effect.fail(new ControlError({
40
- code: "bad_request",
41
- message: `unknown subscription kind: ${params.kind}`
42
- }));
43
- }
44
- const kind = resolved.kind;
45
- let removed = false;
46
- yield* runtimeState.serializeEffect(Effect.gen(function* () {
47
- const directory = resolved.info.connector === "native"
48
- ? defaultSubscriptionAccountDirectory(kind, env)
49
- : undefined;
50
- const nativePath = directory === undefined ? undefined : join(directory, `${params.label}.json`);
51
- if (nativePath !== undefined && existsSync(nativePath)) {
52
- const nativeKind = kind;
53
- const hasRemainingAccount = accountEntries(env).some((entry) => entry.connector === "native" &&
54
- entry.subscriptionKind === nativeKind &&
55
- entry.label !== params.label);
56
- const raw = parseYaml(runtimeState.document);
57
- const providers = typeof raw.providers === "object" &&
58
- raw.providers !== null &&
59
- !Array.isArray(raw.providers)
60
- ? { ...raw.providers }
61
- : {};
62
- const disableProvider = !hasRemainingAccount && runtimeState.config.providers[nativeKind] !== undefined;
63
- if (disableProvider) {
64
- delete providers[nativeKind];
65
- raw.providers = providers;
66
- if (typeof raw.defaultModel === "string" &&
67
- raw.defaultModel.startsWith(`${nativeKind}/`)) {
68
- delete raw.defaultModel;
69
- }
33
+ export const accountMutationHandlers = {
34
+ "accounts.remove": (params) => Effect.gen(function* () {
35
+ const daemonEnv = yield* DaemonEnv;
36
+ const runtimeState = yield* DaemonState;
37
+ const generations = yield* Generations;
38
+ const activity = yield* AccountActivity;
39
+ const authHealth = yield* AccountAuth;
40
+ const sidecar = yield* Sidecar;
41
+ const env = daemonEnv.env;
42
+ const home = daemonEnv.home;
43
+ const configPath = daemonEnv.configPath;
44
+ const replaceRouter = generations.replace;
45
+ const resolved = resolveAccountConnector(params.kind);
46
+ if (resolved === undefined) {
47
+ return yield* Effect.fail(new ControlError({
48
+ code: "bad_request",
49
+ message: `unknown subscription kind: ${params.kind}`
50
+ }));
51
+ }
52
+ const kind = resolved.kind;
53
+ let removed = false;
54
+ yield* runtimeState.serializeEffect(Effect.gen(function* () {
55
+ const directory = resolved.info.connector === "native"
56
+ ? defaultSubscriptionAccountDirectory(kind, env)
57
+ : undefined;
58
+ const nativePath = directory === undefined ? undefined : join(directory, `${params.label}.json`);
59
+ if (nativePath !== undefined && existsSync(nativePath)) {
60
+ const nativeKind = kind;
61
+ const hasRemainingAccount = accountEntries(env).some((entry) => entry.connector === "native" &&
62
+ entry.subscriptionKind === nativeKind &&
63
+ entry.label !== params.label);
64
+ const raw = parseYaml(runtimeState.document);
65
+ const sources = typeof raw.sources === "object" && raw.sources !== null && !Array.isArray(raw.sources)
66
+ ? { ...raw.sources }
67
+ : Object.fromEntries(Object.entries(runtimeState.config.sources).map(([sourceId, source]) => [
68
+ sourceId,
69
+ source
70
+ ]));
71
+ const disableProvider = !hasRemainingAccount &&
72
+ Object.values(runtimeState.config.sources).some((source) => source.use === nativeKind);
73
+ if (disableProvider) {
74
+ for (const [sourceId, source] of Object.entries(sources)) {
75
+ if (typeof source === "object" &&
76
+ source !== null &&
77
+ !Array.isArray(source) &&
78
+ source.use === nativeKind) {
79
+ delete sources[sourceId];
70
80
  }
71
- const document = disableProvider ? stringifyYaml(raw) : runtimeState.document;
72
- const config = disableProvider
73
- ? parseConfigDocument(document)
74
- : runtimeState.config;
75
- const transaction = prepareAccountTransaction({
76
- home,
77
- configPath,
78
- accountPaths: [
79
- nativePath,
80
- join(home, "usage", "account-activity.v1.json"),
81
- join(home, "subscriptions", "account-auth.v1.json")
82
- ],
83
- accountRoots: [dirname(nativePath), home, join(home, "subscriptions")],
84
- kind: nativeKind,
85
- provider: nativeKind,
86
- labels: [params.label]
87
- });
88
- const rollbackMessage = `could not remove ${kind}/${params.label}; rollback failed`;
89
- yield* Effect.gen(function* () {
90
- const outcome = yield* Effect.try({
91
- try: () => {
92
- const result = removeSubscriptionAccount(nativeKind, params.label, {
93
- accountsDirectory: dirname(nativePath)
94
- });
95
- if (!result.removed)
96
- cleanupAccountTransaction(transaction);
97
- return result;
98
- },
99
- catch: toRouteKitFailure
100
- });
101
- removed = outcome.removed;
102
- if (!removed)
103
- return;
104
- yield* replaceRouter(config, document, {
105
- write: disableProvider,
106
- configRevision: disableProvider,
107
- accountRevision: true,
108
- persist: () => Effect.gen(function* () {
109
- yield* activity.remove(subscriptionAccountIdentity(nativeKind, params.label));
110
- yield* authHealth.remove(subscriptionAccountIdentity(nativeKind, params.label));
111
- markAccountTransactionCommitted(transaction);
112
- })
113
- });
114
- yield* Effect.try({
115
- try: () => cleanupAccountTransaction(transaction),
116
- catch: toRouteKitFailure
117
- }).pipe(Effect.ignore);
118
- }).pipe(Effect.catch((error) => rollbackAccountCoordinators(transaction, home, activity, authHealth, error, rollbackMessage)));
119
- return;
120
81
  }
121
- const entry = cliproxyAccountEntries(env).find((candidate) => candidate.label === params.label && cliproxyAccountMatchesKind(candidate, kind));
122
- if (entry === undefined)
123
- return;
124
- const previous = yield* Effect.try({
125
- try: () => readFileSync(entry.path),
126
- catch: toRouteKitFailure
127
- });
128
- const cliproxyResult = yield* Effect.try({
129
- try: () => removeCliproxyAccount(params.label, env),
130
- catch: toRouteKitFailure
131
- });
132
- removed = cliproxyResult.removed;
133
- if (!removed)
134
- return;
135
- yield* Effect.gen(function* () {
136
- yield* sidecar.refresh;
137
- yield* replaceRouter(runtimeState.config, runtimeState.document, {
138
- write: false,
139
- accountRevision: true
140
- });
141
- }).pipe(Effect.catch((error) => Effect.gen(function* () {
142
- yield* Effect.try({
143
- try: () => {
144
- writeFileAtomic(entry.path, previous.toString("utf8"), { mode: 0o600 });
145
- chmodSync(entry.path, 0o600);
146
- },
147
- catch: toRouteKitFailure
148
- });
149
- yield* sidecar.refresh.pipe(Effect.ignore);
150
- return yield* Effect.fail(routeKitError(error));
151
- })));
152
- }));
153
- return { removed, revision: runtimeState.revisions.accounts };
154
- }),
155
- "accounts.rename": (params) => Effect.gen(function* () {
156
- const { env: daemonEnv, state: runtimeState, generations, activity, auth: authHealth } = yield* daemonAccountServices;
157
- const host = yield* DaemonHost;
158
- const env = daemonEnv.env;
159
- const home = daemonEnv.home;
160
- const configPath = daemonEnv.configPath;
161
- const replaceRouter = generations.replace;
162
- const resolved = resolveAccountConnector(params.kind);
163
- if (resolved === undefined || resolved.info.connector !== "native") {
164
- return yield* Effect.fail(new ControlError({
165
- code: "bad_request",
166
- message: "account rename supports only claude-code and codex"
167
- }));
168
- }
169
- for (const [field, label] of [
170
- ["source", params.source],
171
- ["target", params.target]
172
- ]) {
173
- if (sanitizeSubscriptionLabel(label) !== label || label.startsWith(".")) {
174
- return yield* Effect.fail(new ControlError({
175
- code: "bad_request",
176
- message: `${field} account label must already be normalized`
177
- }));
82
+ raw.sources = sources;
83
+ delete raw.providers;
84
+ if (typeof raw.defaultModel === "string" &&
85
+ raw.defaultModel.startsWith(`${nativeKind}/`)) {
86
+ delete raw.defaultModel;
178
87
  }
179
88
  }
180
- const kind = resolved.kind;
181
- yield* runtimeState.serializeEffect(Effect.gen(function* () {
182
- const directory = defaultSubscriptionAccountDirectory(kind, env);
183
- const sourcePath = join(directory, `${params.source}.json`);
184
- const targetPath = join(directory, `${params.target}.json`);
185
- yield* Effect.try({
89
+ const document = disableProvider ? stringifyYaml(raw) : runtimeState.document;
90
+ const config = disableProvider ? parseConfigDocument(document) : runtimeState.config;
91
+ const transaction = prepareAccountTransaction({
92
+ home,
93
+ configPath,
94
+ accountPaths: [
95
+ nativePath,
96
+ join(home, "usage", "account-activity.v1.json"),
97
+ join(home, "subscriptions", "account-auth.v1.json")
98
+ ],
99
+ accountRoots: [dirname(nativePath), home, join(home, "subscriptions")],
100
+ kind: nativeKind,
101
+ provider: nativeKind,
102
+ labels: [params.label]
103
+ });
104
+ const rollbackMessage = `could not remove ${kind}/${params.label}; rollback failed`;
105
+ yield* Effect.gen(function* () {
106
+ const outcome = yield* Effect.try({
186
107
  try: () => {
187
- if (!existsSync(sourcePath)) {
188
- throw new ControlError({
189
- code: "not_found",
190
- message: `${kind}/${params.source} is not enrolled`
191
- });
192
- }
193
- try {
194
- lstatSync(targetPath);
195
- throw new ControlError({
196
- code: "conflict",
197
- message: `${kind}/${params.target} is already enrolled`
198
- });
199
- }
200
- catch (error) {
201
- if (error instanceof ControlError ||
202
- typeof error !== "object" ||
203
- error === null ||
204
- !("code" in error) ||
205
- error.code !== "ENOENT") {
206
- throw error;
207
- }
208
- }
108
+ const result = removeSubscriptionAccount(nativeKind, params.label, {
109
+ accountsDirectory: dirname(nativePath)
110
+ });
111
+ if (!result.removed)
112
+ cleanupAccountTransaction(transaction);
113
+ return result;
209
114
  },
210
115
  catch: toRouteKitFailure
211
116
  });
212
- const transaction = prepareAccountTransaction({
213
- home,
214
- configPath,
215
- accountPaths: [
216
- sourcePath,
217
- targetPath,
218
- join(directory, ".state.json"),
219
- join(home, "usage", "account-activity.v1.json"),
220
- join(home, "subscriptions", "account-auth.v1.json")
221
- ],
222
- accountRoots: [directory, home, join(home, "subscriptions")],
223
- kind,
224
- provider: kind,
225
- labels: [params.source, params.target]
226
- });
227
- yield* Effect.gen(function* () {
228
- yield* Effect.try({
229
- try: () => {
230
- renameSubscriptionAccount(kind, params.source, params.target, {
231
- accountsDirectory: directory
232
- });
233
- },
234
- catch: toRouteKitFailure
235
- });
236
- const tracker = yield* RateLimitTracker.open(join(directory, ".state.json"), kind);
237
- yield* tracker.renameMember(params.source, params.target);
238
- host.onAccountTransactionPhase?.("credentials-written");
239
- yield* replaceRouter(runtimeState.config, runtimeState.document, {
240
- write: false,
241
- accountRevision: true,
242
- persist: () => Effect.gen(function* () {
243
- yield* activity.rename(subscriptionAccountIdentity(kind, params.source), subscriptionAccountIdentity(kind, params.target));
244
- yield* authHealth.rename(subscriptionAccountIdentity(kind, params.source), subscriptionAccountIdentity(kind, params.target));
245
- markAccountTransactionCommitted(transaction);
246
- })
247
- });
248
- yield* Effect.try({
249
- try: () => cleanupAccountTransaction(transaction),
250
- catch: toRouteKitFailure
251
- }).pipe(Effect.ignore);
252
- }).pipe(Effect.catch((error) => rollbackAccountCoordinators(transaction, home, activity, authHealth, error, `could not rename ${kind}/${params.source}; rollback failed`)));
253
- }));
254
- return { renamed: true, revision: runtimeState.revisions.accounts };
255
- }),
256
- "accounts.sync": () => Effect.gen(function* () {
257
- const { state: runtimeState, generations, sidecar } = yield* daemonAccountServices;
258
- return yield* runtimeState.serializeEffect(Effect.gen(function* () {
259
- yield* sidecar.refresh;
260
- yield* generations.replace(runtimeState.config, runtimeState.document, {
261
- write: false,
262
- accountRevision: true
117
+ removed = outcome.removed;
118
+ if (!removed)
119
+ return;
120
+ yield* replaceRouter(config, document, {
121
+ write: disableProvider,
122
+ configRevision: disableProvider,
123
+ accountRevision: true,
124
+ persist: () => Effect.gen(function* () {
125
+ yield* activity.remove(subscriptionAccountIdentity(nativeKind, params.label));
126
+ yield* authHealth.remove(subscriptionAccountIdentity(nativeKind, params.label));
127
+ markAccountTransactionCommitted(transaction);
128
+ })
263
129
  });
264
- return { synced: true, revision: runtimeState.revisions.accounts };
130
+ yield* Effect.try({
131
+ try: () => cleanupAccountTransaction(transaction),
132
+ catch: toRouteKitFailure
133
+ }).pipe(Effect.ignore);
134
+ }).pipe(Effect.catch((error) => rollbackAccountCoordinators(transaction, home, activity, authHealth, error, rollbackMessage)));
135
+ return;
136
+ }
137
+ const entry = cliproxyAccountEntries(env).find((candidate) => candidate.label === params.label && cliproxyAccountMatchesKind(candidate, kind));
138
+ if (entry === undefined)
139
+ return;
140
+ const previous = yield* Effect.try({
141
+ try: () => readFileSync(entry.path),
142
+ catch: toRouteKitFailure
143
+ });
144
+ const cliproxyResult = yield* Effect.try({
145
+ try: () => removeCliproxyAccount(params.label, env),
146
+ catch: toRouteKitFailure
147
+ });
148
+ removed = cliproxyResult.removed;
149
+ if (!removed)
150
+ return;
151
+ yield* Effect.gen(function* () {
152
+ yield* sidecar.refresh;
153
+ yield* replaceRouter(runtimeState.config, runtimeState.document, {
154
+ write: false,
155
+ accountRevision: true
156
+ });
157
+ }).pipe(Effect.catch((error) => Effect.gen(function* () {
158
+ yield* Effect.try({
159
+ try: () => {
160
+ writeFileAtomic(entry.path, previous.toString("utf8"), { mode: 0o600 });
161
+ chmodSync(entry.path, 0o600);
162
+ },
163
+ catch: toRouteKitFailure
164
+ });
165
+ yield* sidecar.refresh.pipe(Effect.ignore);
166
+ return yield* Effect.fail(routeKitError(error));
167
+ })));
168
+ }));
169
+ return { removed, revision: runtimeState.revisions.accounts };
170
+ }),
171
+ "accounts.rename": (params) => Effect.gen(function* () {
172
+ const daemonEnv = yield* DaemonEnv;
173
+ const runtimeState = yield* DaemonState;
174
+ const generations = yield* Generations;
175
+ const activity = yield* AccountActivity;
176
+ const authHealth = yield* AccountAuth;
177
+ const host = yield* DaemonHost;
178
+ const env = daemonEnv.env;
179
+ const home = daemonEnv.home;
180
+ const configPath = daemonEnv.configPath;
181
+ const replaceRouter = generations.replace;
182
+ const resolved = resolveAccountConnector(params.kind);
183
+ if (resolved === undefined || resolved.info.connector !== "native") {
184
+ return yield* Effect.fail(new ControlError({
185
+ code: "bad_request",
186
+ message: "account rename supports only claude-code and codex"
187
+ }));
188
+ }
189
+ for (const [field, label] of [
190
+ ["source", params.source],
191
+ ["target", params.target]
192
+ ]) {
193
+ if (sanitizeSubscriptionLabel(label) !== label || label.startsWith(".")) {
194
+ return yield* Effect.fail(new ControlError({
195
+ code: "bad_request",
196
+ message: `${field} account label must already be normalized`
265
197
  }));
266
- }),
267
- "accounts.resetCredits": (params, context) => Effect.gen(function* () {
268
- const { gateway } = yield* daemonAccountServices;
269
- return yield* gateway
270
- .router()
271
- .listResetCredits(params.kind, params.label, context.signal)
272
- .pipe(Effect.map((resetCredits) => ({
273
- kind: params.kind,
274
- label: params.label,
275
- resetCredits
276
- })), Effect.catch((error) => {
277
- const message = error instanceof Error ? error.message : String(error);
278
- if (message.includes("is not enrolled") ||
279
- message.includes("no codex account pool")) {
280
- return Effect.fail(new ControlError({ code: "not_found", message }));
198
+ }
199
+ }
200
+ const kind = resolved.kind;
201
+ yield* runtimeState.serializeEffect(Effect.gen(function* () {
202
+ const directory = defaultSubscriptionAccountDirectory(kind, env);
203
+ const sourcePath = join(directory, `${params.source}.json`);
204
+ const targetPath = join(directory, `${params.target}.json`);
205
+ yield* Effect.try({
206
+ try: () => {
207
+ if (!existsSync(sourcePath)) {
208
+ throw new ControlError({
209
+ code: "not_found",
210
+ message: `${kind}/${params.source} is not enrolled`
211
+ });
281
212
  }
282
- return Effect.fail(routeKitError(error));
283
- }));
284
- }),
285
- "accounts.redeemReset": (params, context) => Effect.gen(function* () {
286
- const { gateway } = yield* daemonAccountServices;
287
- return yield* gateway
288
- .router()
289
- .redeemReset({
290
- kind: params.kind,
291
- label: params.label,
292
- ...(params.creditId !== undefined ? { creditId: params.creditId } : {}),
293
- ...(params.redeemRequestId !== undefined
294
- ? { redeemRequestId: params.redeemRequestId }
295
- : {})
296
- }, context.signal)
297
- .pipe(Effect.map((result) => ({
298
- ok: result.ok,
299
- code: result.code,
300
- kind: "codex",
301
- label: result.label,
302
- redeemRequestId: result.redeemRequestId,
303
- ...(result.creditId !== undefined ? { creditId: result.creditId } : {}),
304
- ...(result.windowsReset !== undefined ? { windowsReset: result.windowsReset } : {}),
305
- usage: result.usage
306
- })), Effect.catch((error) => {
307
- const message = error instanceof Error ? error.message : String(error);
308
- if (message.includes("is not enrolled") || message.includes("no redeemable")) {
309
- return Effect.fail(new ControlError({ code: "not_found", message }));
213
+ try {
214
+ lstatSync(targetPath);
215
+ throw new ControlError({
216
+ code: "conflict",
217
+ message: `${kind}/${params.target} is already enrolled`
218
+ });
310
219
  }
311
- if (message.includes("does not support") ||
312
- message.includes("no codex account pool") ||
313
- message.includes("creditId must not be empty") ||
314
- message.includes("account label is required")) {
315
- return Effect.fail(new ControlError({ code: "bad_request", message }));
220
+ catch (error) {
221
+ if (error instanceof ControlError ||
222
+ typeof error !== "object" ||
223
+ error === null ||
224
+ !("code" in error) ||
225
+ error.code !== "ENOENT") {
226
+ throw error;
227
+ }
316
228
  }
317
- return Effect.fail(new ControlError({ code: "internal", message }));
318
- }));
319
- })
320
- };
321
- }
322
- }
229
+ },
230
+ catch: toRouteKitFailure
231
+ });
232
+ const transaction = prepareAccountTransaction({
233
+ home,
234
+ configPath,
235
+ accountPaths: [
236
+ sourcePath,
237
+ targetPath,
238
+ join(directory, ".state.json"),
239
+ join(home, "usage", "account-activity.v1.json"),
240
+ join(home, "subscriptions", "account-auth.v1.json")
241
+ ],
242
+ accountRoots: [directory, home, join(home, "subscriptions")],
243
+ kind,
244
+ provider: kind,
245
+ labels: [params.source, params.target]
246
+ });
247
+ yield* Effect.gen(function* () {
248
+ yield* Effect.try({
249
+ try: () => {
250
+ renameSubscriptionAccount(kind, params.source, params.target, {
251
+ accountsDirectory: directory
252
+ });
253
+ },
254
+ catch: toRouteKitFailure
255
+ });
256
+ const tracker = yield* RateLimitTracker.open(join(directory, ".state.json"), kind);
257
+ yield* tracker.renameMember(params.source, params.target);
258
+ host.onAccountTransactionPhase?.("credentials-written");
259
+ yield* replaceRouter(runtimeState.config, runtimeState.document, {
260
+ write: false,
261
+ accountRevision: true,
262
+ persist: () => Effect.gen(function* () {
263
+ yield* activity.rename(subscriptionAccountIdentity(kind, params.source), subscriptionAccountIdentity(kind, params.target));
264
+ yield* authHealth.rename(subscriptionAccountIdentity(kind, params.source), subscriptionAccountIdentity(kind, params.target));
265
+ markAccountTransactionCommitted(transaction);
266
+ })
267
+ });
268
+ yield* Effect.try({
269
+ try: () => cleanupAccountTransaction(transaction),
270
+ catch: toRouteKitFailure
271
+ }).pipe(Effect.ignore);
272
+ }).pipe(Effect.catch((error) => rollbackAccountCoordinators(transaction, home, activity, authHealth, error, `could not rename ${kind}/${params.source}; rollback failed`)));
273
+ }));
274
+ return { renamed: true, revision: runtimeState.revisions.accounts };
275
+ }),
276
+ "accounts.sync": () => Effect.gen(function* () {
277
+ const runtimeState = yield* DaemonState;
278
+ const generations = yield* Generations;
279
+ const sidecar = yield* Sidecar;
280
+ return yield* runtimeState.serializeEffect(Effect.gen(function* () {
281
+ yield* sidecar.refresh;
282
+ yield* generations.replace(runtimeState.config, runtimeState.document, {
283
+ write: false,
284
+ accountRevision: true
285
+ });
286
+ return { synced: true, revision: runtimeState.revisions.accounts };
287
+ }));
288
+ }),
289
+ "accounts.resetCredits": (params, context) => Effect.gen(function* () {
290
+ const gateway = yield* ActiveGateway;
291
+ return yield* gateway
292
+ .router()
293
+ .listResetCredits(params.kind, params.label, context.signal)
294
+ .pipe(Effect.map((resetCredits) => ({
295
+ kind: params.kind,
296
+ label: params.label,
297
+ resetCredits
298
+ })), Effect.catch((error) => {
299
+ const message = error instanceof Error ? error.message : String(error);
300
+ if (message.includes("is not enrolled") || message.includes("no codex account pool")) {
301
+ return Effect.fail(new ControlError({ code: "not_found", message }));
302
+ }
303
+ return Effect.fail(routeKitError(error));
304
+ }));
305
+ }),
306
+ "accounts.redeemReset": (params, context) => Effect.gen(function* () {
307
+ const gateway = yield* ActiveGateway;
308
+ return yield* gateway
309
+ .router()
310
+ .redeemReset({
311
+ kind: params.kind,
312
+ label: params.label,
313
+ ...(params.creditId !== undefined ? { creditId: params.creditId } : {}),
314
+ ...(params.redeemRequestId !== undefined
315
+ ? { redeemRequestId: params.redeemRequestId }
316
+ : {})
317
+ }, context.signal)
318
+ .pipe(Effect.map((result) => ({
319
+ ok: result.ok,
320
+ code: result.code,
321
+ kind: "codex",
322
+ label: result.label,
323
+ redeemRequestId: result.redeemRequestId,
324
+ ...(result.creditId !== undefined ? { creditId: result.creditId } : {}),
325
+ ...(result.windowsReset !== undefined ? { windowsReset: result.windowsReset } : {}),
326
+ usage: result.usage
327
+ })), Effect.catch((error) => {
328
+ const message = error instanceof Error ? error.message : String(error);
329
+ if (message.includes("is not enrolled") || message.includes("no redeemable")) {
330
+ return Effect.fail(new ControlError({ code: "not_found", message }));
331
+ }
332
+ if (message.includes("does not support") ||
333
+ message.includes("no codex account pool") ||
334
+ message.includes("creditId must not be empty") ||
335
+ message.includes("account label is required")) {
336
+ return Effect.fail(new ControlError({ code: "bad_request", message }));
337
+ }
338
+ return Effect.fail(new ControlError({ code: "internal", message }));
339
+ }));
340
+ })
341
+ };
@@ -1,7 +1,5 @@
1
1
  import type { EffectRouteKitControlHandlers } from "@velum-labs/routekit-control/effect";
2
2
  type AccountQueryHandlers = Pick<EffectRouteKitControlHandlers, "accounts.list" | "accounts.status" | "accounts.usage">;
3
3
  /** Owns account inventory, live status, and usage queries. */
4
- export declare class AccountQueryService {
5
- handlers(): AccountQueryHandlers;
6
- }
4
+ export declare const accountQueryHandlers: AccountQueryHandlers;
7
5
  export {};