| 1 | /** |
| 2 | * ProviderHost — what a provider package may ask of the server it runs in. |
| 3 | * |
| 4 | * Drivers and adapters run inside a T3 server but must not import it. The |
| 5 | * server provides this one service; everything a provider needs from its |
| 6 | * environment (directories, server settings, background demand, attachment |
| 7 | * files) goes through it, so a provider package depends only on |
| 8 | * `@t3tools/provider-core` and its own protocol code. |
| 9 | * |
| 10 | * @module provider-core/server/host |
| 11 | */ |
| 12 | import type { |
| 13 | BackgroundScope, |
| 14 | ChatAttachment, |
| 15 | ServerSettings, |
| 16 | ServerSettingsError, |
| 17 | } from "@t3tools/contracts"; |
| 18 | import * as Context from "effect/Context"; |
| 19 | import type * as Effect from "effect/Effect"; |
| 20 | import type * as Option from "effect/Option"; |
| 21 | import type * as Scope from "effect/Scope"; |
| 22 | import type * as Stream from "effect/Stream"; |
| 23 | |
| 24 | import type { ProviderCredentialError } from "./errors.ts"; |
| 25 | |
| 26 | export interface ProviderHostPaths { |
| 27 | /** Directory the server was started in; the fallback cwd for sessions without a project. */ |
| 28 | readonly cwd: string; |
| 29 | /** T3 home. */ |
| 30 | readonly baseDir: string; |
| 31 | /** Server-owned state directory under the T3 home. */ |
| 32 | readonly stateDir: string; |
| 33 | /** Scratch space for provider probes and generated helper files. */ |
| 34 | readonly providerStatusCacheDir: string; |
| 35 | /** Where chat attachments are stored; agents that sandbox file reads must be granted it. */ |
| 36 | readonly attachmentsDir: string; |
| 37 | } |
| 38 | |
| 39 | /** One opaque credential blob in the server's secret store. Only the provider decodes it. */ |
| 40 | export interface ProviderCredentials { |
| 41 | /** Instances whose bindings are equal share these credentials. */ |
| 42 | readonly binding: { readonly owner: "t3"; readonly key: string }; |
| 43 | readonly get: Effect.Effect<Option.Option<Uint8Array>, ProviderCredentialError>; |
| 44 | readonly set: (credentials: Uint8Array) => Effect.Effect<void, ProviderCredentialError>; |
| 45 | readonly remove: Effect.Effect<void, ProviderCredentialError>; |
| 46 | } |
| 47 | |
| 48 | export class ProviderHost extends Context.Service< |
| 49 | ProviderHost, |
| 50 | { |
| 51 | readonly paths: ProviderHostPaths; |
| 52 | readonly settings: { |
| 53 | readonly get: Effect.Effect<ServerSettings, ServerSettingsError>; |
| 54 | /** Runs `use` on a settings snapshot that no settings write can change until it finishes. */ |
| 55 | readonly withSnapshot: <A, E, R>( |
| 56 | use: (settings: ServerSettings) => Effect.Effect<A, E, R>, |
| 57 | ) => Effect.Effect<A, E | ServerSettingsError, R>; |
| 58 | /** Every settings change after subscription, starting with the next one. */ |
| 59 | readonly changes: Stream.Stream<ServerSettings>; |
| 60 | /** Changes buffered from the moment the scoped subscription is acquired. */ |
| 61 | readonly subscribe: Effect.Effect<Stream.Stream<ServerSettings>, never, Scope.Scope>; |
| 62 | }; |
| 63 | /** Whether background work for `scope` should run now (client demand and host power). */ |
| 64 | readonly shouldRunBackgroundWork: (scope: BackgroundScope) => Effect.Effect<boolean>; |
| 65 | /** |
| 66 | * Absolute path of a stored chat attachment, or `null` when its id does not |
| 67 | * resolve inside the attachments directory. |
| 68 | */ |
| 69 | readonly resolveAttachmentPath: (attachment: ChatAttachment) => string | null; |
| 70 | /** The credentials stored for `bindingId`, usually an instance id, under a provider-chosen namespace. */ |
| 71 | readonly credentials: ( |
| 72 | namespace: string, |
| 73 | bindingId: string, |
| 74 | ) => Effect.Effect<ProviderCredentials>; |
| 75 | } |
| 76 | >()("@t3tools/provider-core/server/ProviderHost") {} |