@xfey/tutti 0.1.119 → 0.1.121

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 (113) hide show
  1. package/dist/approvals/decision-command.d.ts +2 -2
  2. package/dist/collaboration-state/clarification-snapshot.d.ts +9 -9
  3. package/dist/collaboration-state/task-compile-context.d.ts +1 -1
  4. package/dist/control-plane/workflows/openai.d.ts +3 -4
  5. package/dist/control-plane/workflows/openai.js +3 -2
  6. package/dist/providers/openai/app-server/invocation-coordinator.d.ts +1 -0
  7. package/dist/providers/openai/app-server/invocation-coordinator.js +4 -1
  8. package/dist/providers/openai/app-server/invocation-deadline.d.ts +3 -0
  9. package/dist/providers/openai/app-server/invocation-deadline.js +15 -0
  10. package/dist/providers/openai/app-server/read-only-procedure.d.ts +4 -2
  11. package/dist/providers/openai/app-server/read-only-procedure.js +16 -0
  12. package/dist/providers/openai/app-server/workspace-write-run.d.ts +4 -2
  13. package/dist/providers/openai/app-server/workspace-write-run.js +16 -0
  14. package/dist/providers/openai/chat-assistant.d.ts +3 -4
  15. package/dist/providers/openai/chat-assistant.js +2 -1
  16. package/dist/providers/openai/execution-access.d.ts +17 -0
  17. package/dist/providers/openai/execution-access.js +13 -0
  18. package/dist/providers/openai/sdk-procedure-runner.d.ts +2 -2
  19. package/dist/providers/openai/sdk-procedure-runner.js +17 -0
  20. package/dist/run-pipeline/artifact-applicability.d.ts +3 -4
  21. package/dist/run-pipeline/artifact-applicability.js +2 -1
  22. package/dist/run-pipeline/openai.d.ts +3 -4
  23. package/dist/run-pipeline/openai.js +3 -2
  24. package/dist/run-pipeline/task-run-invocation.js +6 -12
  25. package/dist/server-shell/cli/cli.js +3 -0
  26. package/dist/server-shell/cli/host-lifecycle.js +1 -0
  27. package/dist/server-shell/cli/host-server-runtime.d.ts +1 -0
  28. package/dist/server-shell/cli/host-server-runtime.js +83 -34
  29. package/dist/server-shell/cli/launch-command.js +1 -1
  30. package/dist/server-shell/cli/launch.d.ts +7 -3
  31. package/dist/server-shell/cli/launch.js +23 -4
  32. package/dist/server-shell/cli/machine-local.d.ts +9 -0
  33. package/dist/server-shell/cli/machine-local.js +60 -6
  34. package/dist/server-shell/cli/project-resolver.d.ts +2 -2
  35. package/dist/server-shell/cli/project-resolver.js +6 -3
  36. package/dist/server-shell/cli/relay-registration.js +38 -1
  37. package/dist/server-shell/http/routes/local-control.d.ts +1 -1
  38. package/dist/server-shell/http/routes/local-control.js +2 -46
  39. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +45 -137
  40. package/dist/server-shell/http/routes/project-api/openapi.d.ts +45 -137
  41. package/dist/server-shell/http/routes/project-api/schemas.d.ts +24 -136
  42. package/dist/server-shell/http/routes/project-api/schemas.js +4 -58
  43. package/dist/server-shell/http/routes/project-api/types.d.ts +3 -0
  44. package/dist/server-shell/local-console/managed-project-routes.d.ts +9 -0
  45. package/dist/server-shell/local-console/managed-project-routes.js +120 -0
  46. package/dist/server-shell/local-console/managed-project-service.d.ts +141 -0
  47. package/dist/server-shell/local-console/managed-project-service.js +158 -0
  48. package/dist/server-shell/local-console/operation-coordinator.d.ts +1 -1
  49. package/dist/server-shell/local-console/package-update-hosts.js +2 -1
  50. package/dist/server-shell/local-console/project-service.d.ts +4 -1
  51. package/dist/server-shell/local-console/project-service.js +62 -14
  52. package/dist/server-shell/local-console/server.js +22 -2
  53. package/dist/server-shell/managed-access/execution-binding.d.ts +9 -0
  54. package/dist/server-shell/managed-access/execution-binding.js +22 -0
  55. package/dist/server-shell/managed-access/existing-project.d.ts +15 -0
  56. package/dist/server-shell/managed-access/existing-project.js +41 -0
  57. package/dist/server-shell/managed-access/legacy-records.d.ts +2 -0
  58. package/dist/server-shell/managed-access/legacy-records.js +24 -0
  59. package/dist/server-shell/managed-access/model-control-client.d.ts +65 -0
  60. package/dist/server-shell/managed-access/model-control-client.js +112 -0
  61. package/dist/server-shell/managed-access/private-store.d.ts +19 -0
  62. package/dist/server-shell/managed-access/private-store.js +275 -0
  63. package/dist/server-shell/managed-access/project-provider.d.ts +18 -0
  64. package/dist/server-shell/managed-access/project-provider.js +30 -0
  65. package/dist/server-shell/managed-access/records.d.ts +103 -0
  66. package/dist/server-shell/managed-access/records.js +74 -0
  67. package/dist/server-shell/managed-access/runners.d.ts +11 -0
  68. package/dist/server-shell/managed-access/runners.js +103 -0
  69. package/dist/server-shell/managed-access/runtime-records.d.ts +106 -0
  70. package/dist/server-shell/managed-access/runtime-records.js +65 -0
  71. package/dist/server-shell/managed-access/runtime.d.ts +48 -0
  72. package/dist/server-shell/managed-access/runtime.js +300 -0
  73. package/dist/server-shell/managed-access/state.d.ts +53 -0
  74. package/dist/server-shell/managed-access/state.js +292 -0
  75. package/dist/server-shell/managed-access/transport.d.ts +45 -0
  76. package/dist/server-shell/managed-access/transport.js +39 -0
  77. package/dist/server-shell/managed-access/workspace.d.ts +10 -0
  78. package/dist/server-shell/managed-access/workspace.js +69 -0
  79. package/dist/server-shell/smoke/e2e-client.d.ts +8 -3
  80. package/dist/server-shell/smoke/e2e-client.js +11 -11
  81. package/node_modules/@tutti/relay-client/dist/host-control.d.ts +4 -1
  82. package/node_modules/@tutti/relay-client/dist/host-control.js +26 -3
  83. package/node_modules/@tutti/relay-client/dist/index.d.ts +1 -0
  84. package/node_modules/@tutti/relay-client/dist/index.js +1 -0
  85. package/node_modules/@tutti/relay-client/dist/project-authority.d.ts +70 -0
  86. package/node_modules/@tutti/relay-client/dist/project-authority.js +139 -0
  87. package/node_modules/@tutti/shared/dist/ids/index.d.ts +7 -0
  88. package/node_modules/@tutti/shared/dist/ids/index.js +7 -0
  89. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -0
  90. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -0
  91. package/node_modules/@tutti/shared/dist/schemas/api/managed-account.d.ts +34 -0
  92. package/node_modules/@tutti/shared/dist/schemas/api/managed-account.js +40 -0
  93. package/node_modules/@tutti/shared/dist/schemas/api/provider-config.d.ts +7 -4
  94. package/node_modules/@tutti/shared/dist/schemas/api/provider-config.js +5 -0
  95. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +3 -0
  96. package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.d.ts +26 -0
  97. package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.js +40 -0
  98. package/node_modules/@tutti/shared/dist/schemas/internal/index.d.ts +5 -0
  99. package/node_modules/@tutti/shared/dist/schemas/internal/index.js +5 -0
  100. package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.d.ts +12 -0
  101. package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.js +13 -0
  102. package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.d.ts +17 -0
  103. package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.js +17 -0
  104. package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.d.ts +113 -0
  105. package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.js +84 -0
  106. package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.d.ts +114 -0
  107. package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.js +102 -0
  108. package/package.json +1 -1
  109. package/web/assets/{homepage-motion-scene-B6lCCHBq.js → homepage-motion-scene-WfLyzcCG.js} +1 -1
  110. package/web/assets/{index-DsYi7AhN.css → index-ATsYiBZs.css} +1 -1
  111. package/web/assets/index-o9YCtzEd.js +70 -0
  112. package/web/index.html +2 -2
  113. package/web/assets/index-DepsYMLl.js +0 -70
@@ -3,7 +3,9 @@ import { resolve } from "node:path";
3
3
  import { readRelayProjectMembershipState } from "@tutti/relay-client";
4
4
  import { createProjectSurfaceProfile, } from "@tutti/shared/domain";
5
5
  import { relayProjectRouteSegment } from "@tutti/shared/ids";
6
- import { configureProjectOpenAiProvider, discoverOpenAiModels, isOpenAiApiKey, readOpenAiProviderConfigProjection, validateOpenAiCredential, } from "../../providers/openai/index.js";
6
+ import { hasProjectModelConfiguration, readProjectModelAccess, } from "../managed-access/project-provider.js";
7
+ import { materializeManagedWorkspace } from "../managed-access/workspace.js";
8
+ import { configureProjectOpenAiProvider, discoverOpenAiModels, isOpenAiApiKey, validateOpenAiCredential, } from "../../providers/openai/index.js";
7
9
  import { formatCliErrorReason, LaunchError } from "../cli/errors.js";
8
10
  import { createRuntimeEndpointProbe, waitForHostShutdown, } from "../cli/host-runtime-endpoint.js";
9
11
  import { prepareLaunchProject, resolveLaunchLocalContext, resolveRelayUrl } from "../cli/launch.js";
@@ -207,18 +209,27 @@ export class LocalConsoleProjectService {
207
209
  // Damaged legacy metadata remains visible without an invented creation date.
208
210
  }
209
211
  }
210
- const provider = readOpenAiProviderConfigProjection({
212
+ const provider = readProjectModelAccess({
211
213
  tuttiHome: this.#tuttiHome,
212
214
  projectId: row.project_id,
215
+ relayUrl: binding?.relay_url ?? resolveRelayUrl(operation.env),
213
216
  });
214
- const providerModel = provider.status === "not_configured" ? undefined : provider.default_model;
217
+ const providerModel = provider.status === "not_configured"
218
+ ? undefined
219
+ : provider.status === "managed"
220
+ ? "Tutti"
221
+ : provider.default_model;
215
222
  const activityAt = readProjectActivityAt(this.#tuttiHome, row, binding);
216
- return projectFromRuntimeRow(row, provider.status, providerModel, {
217
- ...(activityAt === undefined ? {} : { activityAt }),
218
- ...(createdAt === undefined ? {} : { createdAt }),
219
- relayUrl: binding?.relay_url ?? relayUrlFromJoinUrl(row.join_url) ?? resolveRelayUrl(operation.env),
220
- surfaceProfile: binding?.surface_profile ?? createProjectSurfaceProfile(),
221
- });
223
+ return {
224
+ ...projectFromRuntimeRow(row, provider.status, providerModel, {
225
+ ...(activityAt === undefined ? {} : { activityAt }),
226
+ ...(createdAt === undefined ? {} : { createdAt }),
227
+ relayUrl: binding?.relay_url ??
228
+ relayUrlFromJoinUrl(row.join_url) ??
229
+ resolveRelayUrl(operation.env),
230
+ surfaceProfile: binding?.surface_profile ?? createProjectSurfaceProfile(),
231
+ }),
232
+ };
222
233
  });
223
234
  }
224
235
  async #readProject(projectId, context) {
@@ -325,12 +336,14 @@ export class LocalConsoleProjectService {
325
336
  #requireStoredLaunchProvider(workspacePath, operation) {
326
337
  const context = resolveLaunchLocalContext({ workspacePath, ...operation });
327
338
  const provider = context.existing_identity.kind === "present"
328
- ? readOpenAiProviderConfigProjection({
339
+ ? readProjectModelAccess({
329
340
  tuttiHome: context.tutti_home,
330
341
  projectId: context.existing_identity.project_id,
342
+ relayUrl: readMachineProjectBinding(context.tutti_home, context.existing_identity.project_id)
343
+ ?.relay_url ?? resolveRelayUrl(operation.env),
331
344
  })
332
345
  : { status: "not_configured" };
333
- if (provider.status !== "configured") {
346
+ if (!hasProjectModelConfiguration(provider)) {
334
347
  throw new LocalConsoleProjectError("provider_configuration_required", "This project does not have a valid saved Provider. Configure it in the launch form.");
335
348
  }
336
349
  }
@@ -359,6 +372,18 @@ export class LocalConsoleProjectService {
359
372
  }
360
373
  async #launchProject(input, context, accountHandoff) {
361
374
  const operation = this.#operationOptions(context);
375
+ if (input.provider !== undefined) {
376
+ const local = resolveLaunchLocalContext({ workspacePath: input.workspacePath, ...operation });
377
+ if (local.existing_identity.kind === "present" &&
378
+ readProjectModelAccess({
379
+ tuttiHome: local.tutti_home,
380
+ projectId: local.existing_identity.project_id,
381
+ relayUrl: readMachineProjectBinding(local.tutti_home, local.existing_identity.project_id)
382
+ ?.relay_url ?? resolveRelayUrl(operation.env),
383
+ }).status === "managed") {
384
+ throw new LocalConsoleProjectError("managed_model_fixed", "Managed projects cannot be changed through BYOK setup.", 409);
385
+ }
386
+ }
362
387
  if (input.provider === undefined) {
363
388
  try {
364
389
  this.#requireStoredLaunchProvider(input.workspacePath, operation);
@@ -388,6 +413,9 @@ export class LocalConsoleProjectService {
388
413
  catch (error) {
389
414
  throw this.#normalizeError(error);
390
415
  }
416
+ if (provider !== undefined && preparation.provider_status === "managed") {
417
+ throw new LocalConsoleProjectError("managed_model_fixed", "Managed projects cannot be changed through BYOK setup.", 409);
418
+ }
391
419
  if (provider !== undefined) {
392
420
  const configured = await configureProjectOpenAiProvider({
393
421
  tuttiHome: preparation.tutti_home,
@@ -401,10 +429,11 @@ export class LocalConsoleProjectService {
401
429
  throw new LocalConsoleProjectError(configured.reason, localConsoleProviderValidationMessage(configured));
402
430
  }
403
431
  }
404
- else if (preparation.provider_status !== "configured") {
432
+ else if (!hasProjectModelConfiguration(preparation.provider_config)) {
405
433
  throw new LocalConsoleProjectError("provider_configuration_required", "This project does not have a valid saved Provider. Configure it in the launch form.");
406
434
  }
407
435
  if (preparation.machine_local.binding.surface_profile.kind === "feishu" &&
436
+ preparation.provider_status !== "managed" &&
408
437
  accountHandoff !== undefined) {
409
438
  writeFeishuOwnerAccountHandoff({
410
439
  tuttiHome: preparation.tutti_home,
@@ -414,12 +443,31 @@ export class LocalConsoleProjectService {
414
443
  now: this.#now(),
415
444
  });
416
445
  }
446
+ return this.#finishLaunch(preparation, context, operation.env);
447
+ }
448
+ // Called only by the native authority service while holding the Console operation
449
+ // guard. It accepts a private, verified journal, never a browser-provided receipt.
450
+ async launchAuthorizedProject(state, requestRef, context) {
451
+ const operation = this.#operationOptions(context);
452
+ const preparation = await materializeManagedWorkspace({
453
+ state,
454
+ requestRef,
455
+ tuttiHome: this.#tuttiHome,
456
+ environment: operation.env,
457
+ now: this.#now,
458
+ });
459
+ return this.#finishLaunch(preparation, context, {
460
+ ...operation.env,
461
+ TUTTI_RELAY_URL: state.origin,
462
+ });
463
+ }
464
+ async #finishLaunch(preparation, context, environment) {
417
465
  try {
418
466
  const ready = await this.#startManagedHost({
419
467
  workspaceRoot: preparation.workspace_root,
420
468
  tuttiHome: preparation.tutti_home,
421
469
  projectId: preparation.project_id,
422
- env: operation.env,
470
+ env: environment,
423
471
  });
424
472
  if (preparation.machine_local.binding.surface_profile.kind === "standalone" &&
425
473
  ready.join_url === undefined) {
@@ -480,7 +528,7 @@ export class LocalConsoleProjectService {
480
528
  }
481
529
  if (!managed.workspace_exists ||
482
530
  managed.runtime_endpoint === null ||
483
- managed.provider_config.status !== "configured") {
531
+ !hasProjectModelConfiguration(managed.provider_config)) {
484
532
  return { project: current, hostUpdate: "deferred_unavailable" };
485
533
  }
486
534
  let shutdownDisposition;
@@ -1,7 +1,10 @@
1
1
  import { timingSafeEqual } from "node:crypto";
2
+ import { LocalConsoleManagedProjectService } from "./managed-project-service.js";
3
+ import { registerLocalConsoleManagedProjectRoutes } from "./managed-project-routes.js";
2
4
  import { existsSync, statSync } from "node:fs";
3
5
  import { resolve } from "node:path";
4
6
  import fastify from "fastify";
7
+ import { createIdempotencyKey } from "@tutti/shared/ids";
5
8
  import { resolveTuttiHome } from "../../providers/openai/index.js";
6
9
  import { readCliVersion } from "../cli/version.js";
7
10
  import { registerHostWebStaticRoutes, resolvePackagedWebStaticRoot } from "../http/static-web.js";
@@ -66,7 +69,11 @@ function requireLoopbackHost(request) {
66
69
  }
67
70
  }
68
71
  export function createLocalConsoleServer(options) {
69
- const app = fastify({ logger: false, bodyLimit: 64 * 1024 });
72
+ const app = fastify({
73
+ logger: false,
74
+ bodyLimit: 64 * 1024,
75
+ genReqId: () => createIdempotencyKey(),
76
+ });
70
77
  const tuttiHome = options.tuttiHome ?? resolveTuttiHome(options.env?.TUTTI_HOME, options.cwd);
71
78
  const sessions = new LocalConsoleSessionRegistry({
72
79
  ...(options.now === undefined ? {} : { now: options.now }),
@@ -119,6 +126,7 @@ export function createLocalConsoleServer(options) {
119
126
  }
120
127
  });
121
128
  app.addHook("onRequest", (request, reply, done) => {
129
+ reply.header("x-tutti-request-id", request.id);
122
130
  try {
123
131
  requireLoopbackHost(request);
124
132
  void reply
@@ -135,7 +143,7 @@ export function createLocalConsoleServer(options) {
135
143
  done(error);
136
144
  }
137
145
  });
138
- app.setErrorHandler((error, _request, reply) => {
146
+ app.setErrorHandler((error, request, reply) => {
139
147
  const normalized = error instanceof LocalConsoleHttpError ||
140
148
  error instanceof LocalConsoleProjectError ||
141
149
  error instanceof LocalConsoleOperationConflictError
@@ -148,6 +156,7 @@ export function createLocalConsoleServer(options) {
148
156
  code: normalized.code,
149
157
  message: normalized.message,
150
158
  retryable: normalized.statusCode >= 500,
159
+ request_id: request.id,
151
160
  },
152
161
  });
153
162
  });
@@ -173,6 +182,17 @@ export function createLocalConsoleServer(options) {
173
182
  options.onActivity?.();
174
183
  return session;
175
184
  }
185
+ registerLocalConsoleManagedProjectRoutes(app, {
186
+ tuttiHome,
187
+ requireSession,
188
+ service: new LocalConsoleManagedProjectService({
189
+ tuttiHome,
190
+ operations,
191
+ projects,
192
+ ...(options.env === undefined ? {} : { serviceEnvironment: options.env }),
193
+ ...(options.now === undefined ? {} : { now: options.now }),
194
+ }),
195
+ });
176
196
  app.get(`${LOCAL_CONSOLE_API_BASE}/health`, () => ({
177
197
  status: "ready",
178
198
  protocol_version: 2,
@@ -0,0 +1,9 @@
1
+ import type { OpenAiCredentialLease, OpenAiExecutionConfig } from "../../providers/openai/execution-access.js";
2
+ import type { NativeManagedRuntime } from "./runtime.js";
3
+ import type { ModelSessionOwner } from "./runtime-records.js";
4
+ export type ManagedExecutionRuntime = Pick<NativeManagedRuntime, "selection" | "run">;
5
+ export declare function bindManagedExecution(runtime: ManagedExecutionRuntime, owner: ModelSessionOwner): Promise<{
6
+ config: OpenAiExecutionConfig;
7
+ credentialLease: OpenAiCredentialLease;
8
+ }>;
9
+ //# sourceMappingURL=execution-binding.d.ts.map
@@ -0,0 +1,22 @@
1
+ import { ManagedLocalStateError } from "./private-store.js";
2
+ export async function bindManagedExecution(runtime, owner) {
3
+ // This happens before provider factories open any durable Thread. Retaining
4
+ // this selection through retries forbids silent route/model replacement.
5
+ const selected = await runtime.selection(owner);
6
+ const config = {
7
+ default_model: selected.provider.model,
8
+ api_base_url: selected.provider.base_url,
9
+ };
10
+ return {
11
+ config,
12
+ credentialLease: (durationMs, invoke) => runtime.run(owner, Math.ceil(durationMs / 1000), async (access) => {
13
+ if (access.session_ref !== selected.session_ref ||
14
+ access.route_ref !== selected.route_ref ||
15
+ access.provider.model !== selected.provider.model ||
16
+ access.provider.base_url !== selected.provider.base_url)
17
+ throw new ManagedLocalStateError("managed_route_changed");
18
+ return invoke(access.provider.api_key);
19
+ }),
20
+ };
21
+ }
22
+ //# sourceMappingURL=execution-binding.js.map
@@ -0,0 +1,15 @@
1
+ import type { ProjectId } from "@tutti/shared/ids";
2
+ export declare function requireStoppedManagedTarget(tuttiHome: string, projectId: ProjectId): void;
3
+ export declare function readExistingManagedTarget(options: {
4
+ tuttiHome: string;
5
+ relayOrigin: string;
6
+ projectId: ProjectId;
7
+ workspaceRoot: string;
8
+ surface: "standalone" | "feishu";
9
+ requireStopped?: boolean;
10
+ }): {
11
+ relayProjectRef: `relay_project_${string}`;
12
+ workspacePath: string;
13
+ hostSecret: string;
14
+ };
15
+ //# sourceMappingURL=existing-project.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "../cli/machine-local.js";
3
+ import { readProjectIdentity } from "../cli/project-identity.js";
4
+ import { ManagedLocalStateError } from "./private-store.js";
5
+ import { managedRelayOrigin } from "./state.js";
6
+ // A provider cutover must never mutate the authority used by a live Host. A
7
+ // process we cannot prove stopped is rejected; network health failure is not proof.
8
+ export function requireStoppedManagedTarget(tuttiHome, projectId) {
9
+ const endpoint = readMachineRuntimeEndpoint(tuttiHome, projectId);
10
+ if (endpoint === null)
11
+ return;
12
+ try {
13
+ process.kill(endpoint.pid, 0);
14
+ }
15
+ catch (error) {
16
+ if (error.code === "ESRCH")
17
+ return;
18
+ }
19
+ throw new ManagedLocalStateError("managed_project_must_be_stopped");
20
+ }
21
+ export function readExistingManagedTarget(options) {
22
+ if (options.requireStopped !== false)
23
+ requireStoppedManagedTarget(options.tuttiHome, options.projectId);
24
+ const binding = readMachineProjectBinding(options.tuttiHome, options.projectId);
25
+ const identity = readProjectIdentity(options.workspaceRoot);
26
+ if (binding === null ||
27
+ binding.relay_project_ref === undefined ||
28
+ binding.relay_url === undefined ||
29
+ managedRelayOrigin(binding.relay_url) !== options.relayOrigin ||
30
+ realpathSync(binding.workspace_root) !== realpathSync(options.workspaceRoot) ||
31
+ binding.surface_profile.kind !== options.surface ||
32
+ identity.kind !== "present" ||
33
+ identity.project_id !== options.projectId)
34
+ throw new ManagedLocalStateError("managed_existing_project_mismatch");
35
+ return {
36
+ relayProjectRef: binding.relay_project_ref,
37
+ workspacePath: binding.workspace_root,
38
+ hostSecret: readHostRegistrationSecret(options.tuttiHome, options.projectId).secret,
39
+ };
40
+ }
41
+ //# sourceMappingURL=existing-project.js.map
@@ -0,0 +1,2 @@
1
+ export declare function upgradeTieredPrivateRecord(value: unknown, withAuthority?: boolean): unknown;
2
+ //# sourceMappingURL=legacy-records.d.ts.map
@@ -0,0 +1,24 @@
1
+ // The only compatibility boundary for 0.1.120 private records. This is not a
2
+ // network parser: callers still validate the complete current strict schema.
3
+ export function upgradeTieredPrivateRecord(value, withAuthority = false) {
4
+ if (!value || typeof value !== "object" || !("version" in value) || value.version !== 1)
5
+ return value;
6
+ if (!("tier" in value) ||
7
+ typeof value.tier !== "string" ||
8
+ !["light", "high", "max"].includes(value.tier))
9
+ return value;
10
+ const { tier, ...fields } = value;
11
+ if (withAuthority && "authority" in fields) {
12
+ const authority = fields.authority;
13
+ if (!authority ||
14
+ typeof authority !== "object" ||
15
+ !("tier" in authority) ||
16
+ authority.tier !== tier)
17
+ return value;
18
+ const projection = { ...authority };
19
+ delete projection.tier;
20
+ return { ...fields, version: 2, authority: projection };
21
+ }
22
+ return { ...fields, version: 2 };
23
+ }
24
+ //# sourceMappingURL=legacy-records.js.map
@@ -0,0 +1,65 @@
1
+ import { ManagedLocalStateError } from "./private-store.js";
2
+ export declare class ManagedControlError extends ManagedLocalStateError {
3
+ readonly status?: number | undefined;
4
+ readonly requestId?: string | undefined;
5
+ constructor(code: string, status?: number | undefined, requestId?: string | undefined);
6
+ }
7
+ export declare class NativeModelControlClient {
8
+ private readonly options;
9
+ constructor(options: {
10
+ authorize: () => Promise<{
11
+ authorization: string;
12
+ control_origin: string;
13
+ }>;
14
+ fetchImpl?: typeof fetch;
15
+ });
16
+ private request;
17
+ pin(sessionRef: string, routeRef?: string): Promise<{
18
+ disposition: "created" | "existing";
19
+ session_ref: string;
20
+ provider: {
21
+ model: string;
22
+ base_url: string;
23
+ };
24
+ route_ref: string;
25
+ }>;
26
+ issue(sessionRef: string, requestRef: string): Promise<{
27
+ disposition: "issued";
28
+ provider: {
29
+ api_key: string;
30
+ model: string;
31
+ base_url: string;
32
+ };
33
+ route_ref: string;
34
+ credential: {
35
+ state: "active" | "revoked" | "expired" | "creating" | "revoking";
36
+ session_ref: string;
37
+ expires_at: number;
38
+ credential_ref: string;
39
+ device_ref: string;
40
+ quota_cap: number;
41
+ };
42
+ } | {
43
+ disposition: "already_issued";
44
+ credential: {
45
+ state: "active" | "revoked" | "expired" | "creating" | "revoking";
46
+ session_ref: string;
47
+ expires_at: number;
48
+ credential_ref: string;
49
+ device_ref: string;
50
+ quota_cap: number;
51
+ };
52
+ }>;
53
+ revoke(credentialRef: string): Promise<{
54
+ disposition: "revoked";
55
+ credential: {
56
+ state: "active" | "revoked" | "expired" | "creating" | "revoking";
57
+ session_ref: string;
58
+ expires_at: number;
59
+ credential_ref: string;
60
+ device_ref: string;
61
+ quota_cap: number;
62
+ };
63
+ }>;
64
+ }
65
+ //# sourceMappingURL=model-control-client.d.ts.map
@@ -0,0 +1,112 @@
1
+ import { Value } from "@sinclair/typebox/value";
2
+ import { ManagedSessionResultSchema, ManagedCredentialIssueResultSchema, ManagedCredentialRevokeResultSchema, ManagedRuntimeCredentialRefSchema, } from "@tutti/shared/schemas/internal";
3
+ import { managedRelayOrigin } from "./state.js";
4
+ import { ManagedLocalStateError } from "./private-store.js";
5
+ export class ManagedControlError extends ManagedLocalStateError {
6
+ status;
7
+ requestId;
8
+ constructor(code, status, requestId) {
9
+ super(code);
10
+ this.status = status;
11
+ this.requestId = requestId;
12
+ }
13
+ }
14
+ const reasons = new Set([
15
+ "authority_invalid",
16
+ "host_delegation_revoked",
17
+ "delegated_installation_fixed",
18
+ "session_not_found",
19
+ "default_route_unavailable",
20
+ "pinned_route_unavailable",
21
+ "gateway_route_drift",
22
+ "credential_request_mismatch",
23
+ "credential_creation_unknown",
24
+ "credential_policy_mismatch",
25
+ "device_credential_exists",
26
+ "credential_limit",
27
+ "credential_revocation_unknown",
28
+ "gateway_transport_unknown",
29
+ "gateway_rate_limited",
30
+ "gateway_rejected",
31
+ "gateway_response_invalid",
32
+ "control_queue_full",
33
+ ]);
34
+ export class NativeModelControlClient {
35
+ options;
36
+ constructor(options) {
37
+ this.options = options;
38
+ }
39
+ async request(path, body, schema) {
40
+ const authority = await this.options.authorize();
41
+ try {
42
+ const origin = managedRelayOrigin(authority.control_origin);
43
+ const response = await (this.options.fetchImpl ?? fetch)(new URL(path, origin), {
44
+ method: "POST",
45
+ redirect: "error",
46
+ signal: AbortSignal.timeout(55_000),
47
+ headers: {
48
+ "content-type": "application/json",
49
+ authorization: `Bearer ${authority.authorization}`,
50
+ },
51
+ body: JSON.stringify(body),
52
+ });
53
+ const header = response.headers.get("x-tutti-request-id");
54
+ const requestId = header && /^[A-Za-z0-9_-]{1,100}$/.test(header) ? header : undefined;
55
+ const reader = response.body?.getReader();
56
+ if (!reader)
57
+ throw new ManagedControlError("managed_control_invalid_response", response.status, requestId);
58
+ const chunks = [];
59
+ let size = 0;
60
+ try {
61
+ for (;;) {
62
+ const item = await reader.read();
63
+ if (item.done)
64
+ break;
65
+ if (!(item.value instanceof Uint8Array))
66
+ throw new ManagedControlError("managed_control_invalid_response", response.status, requestId);
67
+ size += item.value.byteLength;
68
+ if (size > 16 * 1024)
69
+ throw new ManagedControlError("managed_control_invalid_response", response.status, requestId);
70
+ chunks.push(item.value);
71
+ }
72
+ }
73
+ finally {
74
+ await reader.cancel().catch(() => undefined);
75
+ reader.releaseLock();
76
+ }
77
+ let value;
78
+ try {
79
+ value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
80
+ }
81
+ catch {
82
+ throw new ManagedControlError("managed_control_invalid_response", response.status, requestId);
83
+ }
84
+ if (!response.ok) {
85
+ const reason = value?.error?.code;
86
+ throw new ManagedControlError(typeof reason === "string" && reasons.has(reason)
87
+ ? reason
88
+ : "managed_control_unavailable", response.status, requestId);
89
+ }
90
+ if (!Value.Check(schema, value))
91
+ throw new ManagedControlError("managed_control_invalid_response", response.status, requestId);
92
+ return value;
93
+ }
94
+ catch (error) {
95
+ if (error instanceof ManagedLocalStateError)
96
+ throw error;
97
+ throw new ManagedControlError("managed_control_unavailable");
98
+ }
99
+ }
100
+ pin(sessionRef, routeRef) {
101
+ return this.request("/v2/sessions", { session_ref: sessionRef, ...(routeRef === undefined ? {} : { route_ref: routeRef }) }, ManagedSessionResultSchema);
102
+ }
103
+ issue(sessionRef, requestRef) {
104
+ return this.request("/v2/credentials", { session_ref: sessionRef, request_ref: requestRef }, ManagedCredentialIssueResultSchema);
105
+ }
106
+ revoke(credentialRef) {
107
+ if (!Value.Check(ManagedRuntimeCredentialRefSchema, credentialRef))
108
+ throw new ManagedControlError("managed_control_invalid_request");
109
+ return this.request(`/v2/credentials/${credentialRef}/revoke`, {}, ManagedCredentialRevokeResultSchema);
110
+ }
111
+ }
112
+ //# sourceMappingURL=model-control-client.js.map
@@ -0,0 +1,19 @@
1
+ export declare class ManagedLocalStateError extends Error {
2
+ readonly code: string;
3
+ constructor(code: string);
4
+ }
5
+ export declare function isWithin(parent: string, target: string): boolean;
6
+ export declare function ensureManagedPrivateRoot(tuttiHome: string, originHash: string): string;
7
+ export declare class ManagedPrivateStore {
8
+ readonly root: string;
9
+ private readonly identity;
10
+ constructor(root: string);
11
+ private checkRoot;
12
+ private path;
13
+ read(name: string): unknown;
14
+ write(name: string, value: unknown, exclusive?: boolean): boolean;
15
+ transaction<T>(run: () => T): T;
16
+ acquireExecutionLease(name: string): () => void;
17
+ private syncDirectory;
18
+ }
19
+ //# sourceMappingURL=private-store.d.ts.map