diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a7896ddc..72ba9d16 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -87,8 +87,9 @@ Full application layer. Extends core with transport, UI, and CLI. | `daemon/web/server.ts` | Express web server + REST API | | `daemon/web/openai-compat.ts` | OpenAI-compatible `/v1/*` routes (models, chat completions) | | `daemon/web/grpc-web-control.ts` | gRPC client for web server control | -| `daemon/secrets/registry.ts` | `SecretStoreRegistry` (memory + keychain namespaces) | +| `daemon/secrets/registry.ts` | `SecretStoreRegistry` (memory + keychain + file namespaces) | | `daemon/secrets/keychain.ts` | `KeychainSecretStore` (keytar native addon) | +| `daemon/secrets/file-store.ts` | `FileSecretStore` (`/secrets.json`, mode `0600`) | ## Components @@ -206,8 +207,8 @@ interface SecretStore { ``` `CoreState` accepts any `SecretStore` via constructor injection. `DaemonState` -uses `SecretStoreRegistry` (discrete process-lifetime `MemorySecretStore` + -keytar-backed `KeychainSecretStore` namespaces; resolve via +uses `SecretStoreRegistry` (discrete `MemorySecretStore`, keytar-backed +`KeychainSecretStore`, and config-dir `FileSecretStore` namespaces; resolve via `(secret_store, secret_name)`) by default. Tests and library consumers can use `MemorySecretStore` alone. `NamespacedSecretStore` extends the interface for multi-backend `getFrom` / `setIn` / … diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 61a96650..3412bbad 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -388,6 +388,7 @@ Each provider can specify exactly ONE of these (mutually exclusive): - `secret_store` selects the namespace: - `keychain` (default when omitted) → OS keychain - `memory` → process-lifetime daemon memory + - `file` → `/secrets.json` (durable with the config volume; mode `0600`) - `env` → process environment variable named `secret_name` - The same `secret_name` may exist in more than one store; resolve uses only the provider's `secret_store` (no overlay) @@ -409,20 +410,37 @@ providers: secret_store: env # already exported in the process environment ``` +```yaml +providers: + container-openai: + engine: openai + secret_name: "OPENAI_API_KEY" + secret_store: file # persists next to config.yaml on the RW volume +``` + ### Option 1b: In-memory (process-lifetime) via secrets API - Same `secret_name` in the `memory` namespace; set value with `SetSecret` / `POST /api/secrets` and HTTP `secretStore: memory` (SetSecret still cannot write `env`) - Lives only while the daemon process is running -- Independent from keychain — overlapping names are allowed (DR-047) +- Independent from keychain/file — overlapping names are allowed (DR-047) + +### Option 1c: File-backed (config-dir) via secrets API + +- Same API with `secretStore: file` / `secret_store: file` +- Written to `/secrets.json` (override path with `ABBENAY_SECRETS_FILE`) +- Survives daemon restarts when the config directory is on a persistent volume + (e.g. APME Helm `persistence.abbenay.enabled=true`) +- If `secrets.json` exists but is not valid JSON object map, reads treat it as + empty and **writes are refused** so a later `set` cannot wipe the on-disk file ### Recommended workflow (keys are N:1 with providers) -1. **Add a secret** (store) — `SetSecret` with store `memory|keychain`, HTTP +1. **Add a secret** (store) — `SetSecret` with store `memory|keychain|file`, HTTP `secretStore`, **or** export an env var yourself. 2. **Configure the provider** — `secret_name=NAME` and - `secret_store=memory|keychain|env` (YAML/config; HTTP configure uses + `secret_store=memory|keychain|file|env` (YAML/config; HTTP configure uses camelCase `secretName` / `secretStore`). For `env`, the variable is resolved at request time (it need not exist at configure time). 3. Many providers may share one `(secret_store, secret_name)` pair. diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 6ef59b08..3aee834f 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -70,6 +70,10 @@ defaults (or stronger) in place. resolve them. Authenticated clients with the `secrets` capability can still read them for the life of the daemon process. Restart clears them; treat RAM and process dumps accordingly. +- File-backed secrets (DR-048) persist as plaintext JSON at + `/secrets.json` (mode `0600`; override with `ABBENAY_SECRETS_FILE`). + Providers must set `secret_store: file`. Protect the config volume the same + way as `config.yaml`; there is no encryption-at-rest in this revision. --- diff --git a/docs/decisions.md b/docs/decisions.md index 70353180..0ff7fa46 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -861,3 +861,25 @@ store to keychain preserves existing callers. Keys are not 1:1 with providers — configure should pick a secret by name after `SetSecret`. `secret_name` / `secret_store` name the real abstraction; `api_key_keychain_name` was a historical misnomer. + +--- + +## DR-048: Filesystem secret store for durable non-keychain hosts + +**Date:** 2026-08-14 +**Decision:** Add a third writable secret backend, `file` +(`SECRET_STORE_FILE` / `secretStore: "file"`), implemented as +`FileSecretStore` persisting a JSON map at `/secrets.json` +(mode `0600`; override with `ABBENAY_SECRETS_FILE`). It is always registered +in `SecretStoreRegistry` alongside memory and keychain; clients opt in via +`(secret_store, secret_name)`. No encryption-at-rest in this revision — +protection is filesystem permissions and the same RW volume that already +holds `config.yaml` (e.g. container emptyDir or PVC). Default write backend +remains keychain. +**Rationale:** OS keychain is unavailable or awkward in many container / +sidecar deployments (APME Helm Abbenay), and process-lifetime memory is not +durable across restarts. Portal/Gateway databases are not a secrets vault; +Abbenay remains source of truth for provider credentials. Placing secrets +next to config reuses the existing config volume contract without introducing +a separate Gateway SoT or a cloud KMS dependency. Encryption can layer later +without changing the `(secret_store, secret_name)` address model. diff --git a/packages/daemon/src/core/config-schema.ts b/packages/daemon/src/core/config-schema.ts index aee7a5f3..d8c592ca 100644 --- a/packages/daemon/src/core/config-schema.ts +++ b/packages/daemon/src/core/config-schema.ts @@ -85,8 +85,8 @@ export const ProviderConfigSchema = z engine: z.string().min(1), /** Logical secret name (store key or env var). Preferred. */ secret_name: z.string().min(1).optional(), - /** memory | keychain | env — where secret_name is resolved. */ - secret_store: z.enum(['memory', 'keychain', 'env']).optional(), + /** memory | keychain | env | file — where secret_name is resolved. */ + secret_store: z.enum(['memory', 'keychain', 'env', 'file']).optional(), /** @deprecated Use secret_name. */ api_key_keychain_name: z.string().min(1).optional(), /** @deprecated Use secret_name + secret_store: env. */ diff --git a/packages/daemon/src/core/config.ts b/packages/daemon/src/core/config.ts index 3e9418ae..00396dfa 100644 --- a/packages/daemon/src/core/config.ts +++ b/packages/daemon/src/core/config.ts @@ -83,7 +83,7 @@ export interface ProviderConfig { * - `memory` / `keychain` / omitted (with a store name): daemon secret store * - `env`: process environment variable */ - secret_store?: 'memory' | 'keychain' | 'env'; + secret_store?: 'memory' | 'keychain' | 'env' | 'file'; /** * @deprecated Use {@link secret_name}. Kept for existing YAML configs. */ @@ -116,13 +116,13 @@ export function isProviderOwnedSecretName(providerId: string, secretName: string /** * How to resolve credentials for a provider. - * `store` → named backend (memory|keychain) via (secret_store, secret_name); + * `store` → named backend (memory|keychain|file) via (secret_store, secret_name); * `env` → process.env[secret_name]. Omitted secret_store defaults to keychain. */ export function providerCredentialSource( cfg: ProviderConfig, ): - | { kind: 'store'; name: string; backend: 'memory' | 'keychain' } + | { kind: 'store'; name: string; backend: 'memory' | 'keychain' | 'file' } | { kind: 'env'; name: string } | null { if (cfg.secret_store === 'env') { @@ -131,7 +131,10 @@ export function providerCredentialSource( } const storeName = providerSecretName(cfg); if (storeName) { - const backend = cfg.secret_store === 'memory' ? 'memory' : 'keychain'; + const backend = + cfg.secret_store === 'memory' || cfg.secret_store === 'file' + ? cfg.secret_store + : 'keychain'; return { kind: 'store', name: storeName, backend }; } if (cfg.api_key_env_var_name) { diff --git a/packages/daemon/src/core/paths.ts b/packages/daemon/src/core/paths.ts index c18aa7d4..fc33f0fd 100644 --- a/packages/daemon/src/core/paths.ts +++ b/packages/daemon/src/core/paths.ts @@ -19,6 +19,7 @@ * Linux: $XDG_CONFIG_HOME/abbenay → ~/.config/abbenay * macOS: ~/Library/Application Support/abbenay * Windows: %APPDATA%/abbenay + * Holds config.yaml and secrets.json (file secret store) * * Workspace config dir * All: /.config/abbenay @@ -159,6 +160,18 @@ export function getUserConfigPath(): string { return path.join(getConfigDir(), 'config.yaml'); } +/** + * File-backed secrets path: /secrets.json + * + * Override with ``ABBENAY_SECRETS_FILE`` (tests / custom mounts). Lives next to + * ``config.yaml`` so the same RW volume persists providers and secrets. + */ +export function getSecretsPath(): string { + const override = process.env.ABBENAY_SECRETS_FILE?.trim(); + if (override) return override; + return path.join(getConfigDir(), 'secrets.json'); +} + /** Workspace config file: /config.yaml */ export function getWorkspaceConfigPath(workspacePath: string): string { return path.join(getWorkspaceConfigDir(workspacePath), 'config.yaml'); diff --git a/packages/daemon/src/core/secrets.ts b/packages/daemon/src/core/secrets.ts index f962a49f..76abfead 100644 --- a/packages/daemon/src/core/secrets.ts +++ b/packages/daemon/src/core/secrets.ts @@ -2,8 +2,8 @@ * Secret store interface and in-memory implementation. * * Core consumers inject their own SecretStore implementation. - * The daemon uses SecretStoreRegistry (MemorySecretStore + KeychainSecretStore); - * tests/library use MemorySecretStore. + * The daemon uses SecretStoreRegistry (MemorySecretStore + KeychainSecretStore + * + FileSecretStore); tests/library use MemorySecretStore. * * Credential aggregation (finding A1): one daemon may hold many provider keys. * Mutating APIs must stay auth-gated; use {@link auditSecretChange} so operators @@ -56,7 +56,7 @@ export interface SecretAuditEvent { /** Secret key name only — never the value */ key: string; op: 'set' | 'delete'; - /** http-secrets | http-secrets-memory | grpc-secrets | grpc-secrets-memory | http-configure | grpc-configure | core-add */ + /** http-secrets | http-secrets-memory | http-secrets-file | grpc-secrets | grpc-secrets-memory | grpc-secrets-file | http-configure | grpc-configure | core-add */ source: string; actor?: string; } @@ -64,8 +64,10 @@ export interface SecretAuditEvent { /** * Emit an audit log line for a secret mutation (A1 accountability). * Never logs the secret value. - * Sources include: http-secrets | http-secrets-memory | grpc-secrets | - * grpc-secrets-memory | http-configure | grpc-configure | core-add + * Sources include: http-secrets | http-secrets-memory | http-secrets-file | + * grpc-secrets | grpc-secrets-memory | grpc-secrets-file | + * http-configure | http-configure-memory | http-configure-file | + * grpc-configure | grpc-configure-memory | grpc-configure-file | core-add */ export function auditSecretChange(event: SecretAuditEvent): void { const safeKey = sanitizeForLog(event.key); diff --git a/packages/daemon/src/daemon/secrets/file-store.test.ts b/packages/daemon/src/daemon/secrets/file-store.test.ts new file mode 100644 index 00000000..b2be5e11 --- /dev/null +++ b/packages/daemon/src/daemon/secrets/file-store.test.ts @@ -0,0 +1,110 @@ +/** + * Unit tests for FileSecretStore (config-dir secrets.json). + */ + +import * as fsp from 'node:fs/promises'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +const fsMocks = vi.hoisted(() => ({ + rename: vi.fn(), + actualRename: undefined as typeof import('node:fs/promises').rename | undefined, +})); + +vi.mock('node:fs/promises', async (importOriginal) => { + const actual = await importOriginal(); + fsMocks.actualRename = actual.rename.bind(actual); + fsMocks.rename.mockImplementation(fsMocks.actualRename); + return { ...actual, rename: fsMocks.rename }; +}); + +import { FileSecretStore } from './file-store.js'; + +describe('FileSecretStore', () => { + let dir: string; + let filePath: string; + let store: FileSecretStore; + + beforeEach(async () => { + fsMocks.rename.mockReset(); + fsMocks.rename.mockImplementation(fsMocks.actualRename!); + dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'abbenay-secrets-')); + filePath = path.join(dir, 'secrets.json'); + store = new FileSecretStore(filePath); + }); + + afterEach(async () => { + await fsp.rm(dir, { recursive: true, force: true }); + }); + + it('set/get/has/delete round-trip', async () => { + expect(await store.has('K')).toBe(false); + await store.set('K', 'secret-value'); + expect(await store.has('K')).toBe(true); + expect(await store.get('K')).toBe('secret-value'); + expect(await store.delete('K')).toBe(true); + expect(await store.get('K')).toBeNull(); + expect(await store.delete('K')).toBe(false); + }); + + it('persists across store instances (reload from disk)', async () => { + await store.set('OPENROUTER_API_KEY', 'sk-or-test'); + const reloaded = new FileSecretStore(filePath); + expect(await reloaded.get('OPENROUTER_API_KEY')).toBe('sk-or-test'); + }); + + it.skipIf(process.platform === 'win32')('writes mode 0600 on unix', async () => { + await store.set('K', 'v'); + const st = await fsp.stat(filePath); + expect(st.mode & 0o777).toBe(0o600); + }); + + it('starts empty when file is missing', async () => { + expect(await store.get('missing')).toBeNull(); + }); + + it('refuses writes when file is corrupt JSON', async () => { + await fsp.writeFile(filePath, 'not-json{', 'utf8'); + const reloaded = new FileSecretStore(filePath); + expect(await reloaded.get('K')).toBeNull(); + await expect(reloaded.set('K', 'v')).rejects.toThrow(/unparseable/); + expect(await fsp.readFile(filePath, 'utf8')).toBe('not-json{'); + }); + + it('refuses writes when file is a JSON array', async () => { + await fsp.writeFile(filePath, '[]', 'utf8'); + const reloaded = new FileSecretStore(filePath); + expect(await reloaded.get('K')).toBeNull(); + await expect(reloaded.set('K', 'v')).rejects.toThrow(/unparseable/); + }); + + it('exposes absolute path', () => { + expect(store.path).toBe(filePath); + }); + + it('serializes concurrent writes so all keys survive', async () => { + await Promise.all([ + store.set('A', '1'), + store.set('B', '2'), + store.set('C', '3'), + store.delete('missing'), + ]); + expect(await store.get('A')).toBe('1'); + expect(await store.get('B')).toBe('2'); + expect(await store.get('C')).toBe('3'); + const reloaded = new FileSecretStore(filePath); + expect(await reloaded.get('A')).toBe('1'); + expect(await reloaded.get('B')).toBe('2'); + expect(await reloaded.get('C')).toBe('3'); + }); + + it('leaves cache consistent with disk when persist fails', async () => { + await store.set('K', 'ok'); + fsMocks.rename.mockRejectedValueOnce(new Error('disk full')); + await expect(store.set('K', 'bad')).rejects.toThrow(/Failed to persist/); + expect(await store.get('K')).toBe('ok'); + const reloaded = new FileSecretStore(filePath); + expect(await reloaded.get('K')).toBe('ok'); + }); +}); diff --git a/packages/daemon/src/daemon/secrets/file-store.ts b/packages/daemon/src/daemon/secrets/file-store.ts new file mode 100644 index 00000000..e26b4f94 --- /dev/null +++ b/packages/daemon/src/daemon/secrets/file-store.ts @@ -0,0 +1,172 @@ +/** + * Filesystem-backed secret store. + * + * Persists a JSON map of key → value next to Abbenay config + * (``/secrets.json``, mode ``0o600``). Intended for containers + * where the OS keychain is unavailable and process-lifetime memory is not + * durable across restarts. + * + * Mutations and reads are serialized per instance so concurrent set/delete + * cannot clobber each other and get/has observe the latest committed snapshot. + * The in-memory cache is updated only after a successful atomic rename to disk. + * + * If ``secrets.json`` exists but is unparseable, reads treat it as empty and + * writes are refused so a later ``set`` cannot wipe recoverable on-disk data. + * + * Never logs secret values. + */ + +import { randomUUID } from 'node:crypto'; +import * as fsp from 'node:fs/promises'; +import * as path from 'node:path'; + +import type { SecretStore } from '../../core/secrets.js'; +import { getSecretsPath } from '../../core/paths.js'; + +const FILE_MODE = 0o600; +const DIR_MODE = 0o700; + +export class FileSecretStore implements SecretStore { + private readonly filePath: string; + private cache: Map | null = null; + /** Shared in-flight disk load so concurrent first readers do not race. */ + private loadPromise: Promise> | null = null; + /** True when the file exists but is not a JSON object map. */ + private unparseable = false; + /** Chains get/set/delete so concurrent ops apply in order. */ + private writeChain: Promise = Promise.resolve(); + + constructor(filePath?: string) { + this.filePath = filePath ?? getSecretsPath(); + } + + /** Absolute path used for persistence (tests / diagnostics). */ + get path(): string { + return this.filePath; + } + + private async readFromDisk(): Promise> { + const map = new Map(); + try { + const raw = await fsp.readFile(this.filePath, 'utf8'); + const parsed: unknown = JSON.parse(raw); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + for (const [key, value] of Object.entries(parsed as Record)) { + if (typeof value === 'string') { + map.set(key, value); + } + } + } else { + this.unparseable = true; + console.warn( + `[Secrets] Ignoring non-object secrets file at ${this.filePath}; starting empty`, + ); + } + } catch (error: unknown) { + const err = error as NodeJS.ErrnoException; + if (err?.code !== 'ENOENT') { + this.unparseable = true; + const loadError = error instanceof Error ? error.message : String(error); + console.warn( + `[Secrets] Failed to read secrets file ${this.filePath}: ${loadError}; starting empty`, + ); + } + } + return map; + } + + private async ensureLoaded(): Promise> { + if (this.cache) return this.cache; + if (!this.loadPromise) { + this.loadPromise = this.readFromDisk().finally(() => { + this.loadPromise = null; + }); + } + const map = await this.loadPromise; + // A concurrent write may have committed a newer snapshot while we loaded. + if (this.cache) return this.cache; + this.cache = map; + return map; + } + + private assertWritable(): void { + if (this.unparseable) { + throw new Error( + `Refusing to overwrite unparseable secrets file at ${this.filePath}; ` + + 'fix or remove the file, then retry', + ); + } + } + + private async persist(map: Map): Promise { + const dir = path.dirname(this.filePath); + await fsp.mkdir(dir, { recursive: true, mode: DIR_MODE }); + + const payload = JSON.stringify(Object.fromEntries(map), null, 2); + const tmpPath = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`; + try { + await fsp.writeFile(tmpPath, payload, { encoding: 'utf8', mode: FILE_MODE }); + await fsp.rename(tmpPath, this.filePath); + try { + await fsp.chmod(this.filePath, FILE_MODE); + } catch { + // Windows and some FS ignore chmod; best-effort. + } + } catch (error: unknown) { + try { + await fsp.unlink(tmpPath); + } catch { + // ignore cleanup failure + } + const msg = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to persist secrets file: ${msg}`); + } + } + + private enqueueWrite(op: () => Promise): Promise { + const run = this.writeChain.then(op, op); + // Keep the chain alive even if this op rejects (caller still sees the error). + this.writeChain = run.then( + () => undefined, + () => undefined, + ); + return run; + } + + async get(key: string): Promise { + return this.enqueueWrite(async () => { + const map = await this.ensureLoaded(); + return map.get(key) ?? null; + }); + } + + async set(key: string, value: string): Promise { + await this.enqueueWrite(async () => { + const next = new Map(await this.ensureLoaded()); + this.assertWritable(); + next.set(key, value); + await this.persist(next); + this.cache = next; + }); + } + + async delete(key: string): Promise { + return this.enqueueWrite(async () => { + const next = new Map(await this.ensureLoaded()); + this.assertWritable(); + const existed = next.delete(key); + if (existed) { + await this.persist(next); + this.cache = next; + } + return existed; + }); + } + + async has(key: string): Promise { + return this.enqueueWrite(async () => { + const map = await this.ensureLoaded(); + return map.has(key); + }); + } +} diff --git a/packages/daemon/src/daemon/secrets/keychain.test.ts b/packages/daemon/src/daemon/secrets/keychain.test.ts index f5d6217e..28387240 100644 --- a/packages/daemon/src/daemon/secrets/keychain.test.ts +++ b/packages/daemon/src/daemon/secrets/keychain.test.ts @@ -37,6 +37,7 @@ type KeychainInternals = { deletePassword: typeof mocks.deletePassword; } | null; loadError: string | null; + loadPromise: Promise | null; loadKeytarFromSea: () => { getPassword: typeof mocks.getPassword; setPassword: typeof mocks.setPassword; @@ -83,6 +84,8 @@ describe('KeychainSecretStore', () => { throw importError; }, })); + // Drop any successful keytar module from an earlier test (flake under --coverage). + vi.resetModules(); const { KeychainSecretStore } = await import('./keychain.js'); const store = new KeychainSecretStore(); await vi.dynamicImportSettled(); @@ -103,6 +106,7 @@ describe('KeychainSecretStore', () => { const internals = store as unknown as KeychainInternals; internals.keytar = null; internals.loadError = null; + internals.loadPromise = null; setup?.(internals); return store; } @@ -416,5 +420,6 @@ describe('KeychainSecretStore', () => { // Prefer sticky loadError over console.warn — parallel suites can clobber spies. expect(internals.keytar).toBeNull(); expect(internals.loadError).toBe('native addon missing'); + expect(mocks.getPassword).not.toHaveBeenCalled(); }); }); diff --git a/packages/daemon/src/daemon/secrets/keychain.ts b/packages/daemon/src/daemon/secrets/keychain.ts index d91def17..9630a874 100644 --- a/packages/daemon/src/daemon/secrets/keychain.ts +++ b/packages/daemon/src/daemon/secrets/keychain.ts @@ -35,6 +35,8 @@ function isSea(): boolean { export class KeychainSecretStore implements SecretStore { private keytar: typeof import('keytar') | null = null; private loadError: string | null = null; + /** Shared in-flight import so constructor + first get/set cannot race. */ + private loadPromise: Promise | null = null; constructor() { // Load keytar lazily since it's a native module @@ -44,7 +46,13 @@ export class KeychainSecretStore implements SecretStore { private async loadKeytar(): Promise { if (this.keytar) return this.keytar; if (this.loadError) return null; - + if (!this.loadPromise) { + this.loadPromise = this.importKeytar(); + } + return this.loadPromise; + } + + private async importKeytar(): Promise { try { if (isSea()) { // SEA mode: keytar.node ships alongside the executable diff --git a/packages/daemon/src/daemon/secrets/registry.test.ts b/packages/daemon/src/daemon/secrets/registry.test.ts index 57f329eb..0e363ef3 100644 --- a/packages/daemon/src/daemon/secrets/registry.test.ts +++ b/packages/daemon/src/daemon/secrets/registry.test.ts @@ -8,14 +8,16 @@ import { SecretStoreRegistry, parseSecretStoreChoice, requireNamespacedMemory, + secretAuditSource, secretBackendToProto, } from './registry.js'; function createRegistry() { const memory = new MemorySecretStore(); const keychain = new MemorySecretStore(); // stand-in for keychain in unit tests - const registry = new SecretStoreRegistry(memory, keychain); - return { registry, memory, keychain }; + const file = new MemorySecretStore(); // stand-in for file store in unit tests + const registry = new SecretStoreRegistry(memory, keychain, file); + return { registry, memory, keychain, file }; } describe('SecretStoreRegistry', () => { @@ -32,6 +34,24 @@ describe('SecretStoreRegistry', () => { expect(await registry.locateAll('K')).toEqual(['memory', 'keychain']); }); + it('setIn file is discrete from memory and keychain', async () => { + const { registry, memory, keychain, file } = createRegistry(); + await registry.setIn('keychain', 'K', 'kc'); + await registry.setIn('memory', 'K', 'mem'); + await registry.setIn('file', 'K', 'disk'); + + expect(await memory.get('K')).toBe('mem'); + expect(await keychain.get('K')).toBe('kc'); + expect(await file.get('K')).toBe('disk'); + expect(await registry.locateAll('K')).toEqual(['memory', 'keychain', 'file']); + }); + + it('secretBackendToProto maps file to 4', () => { + expect(secretBackendToProto('file')).toBe(4); + expect(secretBackendToProto('memory')).toBe(3); + expect(secretBackendToProto('keychain')).toBe(1); + }); + it('setIn memory does not clear keychain (discrete namespaces)', async () => { const { registry, memory, keychain } = createRegistry(); await registry.setIn('keychain', 'K', 'persistent'); @@ -100,6 +120,12 @@ describe('parseSecretStoreChoice', () => { } }); + it('accepts file', () => { + for (const store of ['file', 'SECRET_STORE_FILE', 4, '4']) { + expect(parseSecretStoreChoice(store)).toEqual({ ok: true, backend: 'file' }); + } + }); + it('rejects ENV writes', () => { const result = parseSecretStoreChoice('SECRET_STORE_ENV'); expect(result.ok).toBe(false); @@ -122,11 +148,17 @@ describe('parseSecretStoreChoice', () => { }); describe('requireNamespacedMemory', () => { - it('allows memory when a registry is present', async () => { + it('allows memory and file when a registry is present', async () => { const { MemorySecretStore } = await import('../../core/secrets.js'); - const registry = new SecretStoreRegistry(new MemorySecretStore(), new MemorySecretStore()); + const registry = new SecretStoreRegistry( + new MemorySecretStore(), + new MemorySecretStore(), + new MemorySecretStore(), + ); expect(requireNamespacedMemory(registry, 'memory')).toEqual({ ok: true }); + expect(requireNamespacedMemory(registry, 'file')).toEqual({ ok: true }); expect(requireNamespacedMemory(registry, 'keychain')).toEqual({ ok: true }); + expect(registry.fileStore).toBeDefined(); }); it('rejects memory when only a plain secret store is available', () => { @@ -137,12 +169,29 @@ describe('requireNamespacedMemory', () => { } expect(requireNamespacedMemory(null, 'keychain')).toEqual({ ok: true }); }); + + it('rejects file when only a plain secret store is available', () => { + const result = requireNamespacedMemory(null, 'file'); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatch(/file|SecretStoreRegistry/i); + } + }); }); describe('secretBackendToProto', () => { it('maps backends to proto enum numbers', () => { expect(secretBackendToProto('keychain')).toBe(1); expect(secretBackendToProto('memory')).toBe(3); + expect(secretBackendToProto('file')).toBe(4); expect(secretBackendToProto(null)).toBe(0); }); }); + +describe('secretAuditSource', () => { + it('suffixes memory and file; keychain keeps the base label', () => { + expect(secretAuditSource('grpc-secrets', 'keychain')).toBe('grpc-secrets'); + expect(secretAuditSource('grpc-secrets', 'memory')).toBe('grpc-secrets-memory'); + expect(secretAuditSource('http-configure', 'file')).toBe('http-configure-file'); + }); +}); diff --git a/packages/daemon/src/daemon/secrets/registry.ts b/packages/daemon/src/daemon/secrets/registry.ts index 050e5923..2cb48bba 100644 --- a/packages/daemon/src/daemon/secrets/registry.ts +++ b/packages/daemon/src/daemon/secrets/registry.ts @@ -1,5 +1,5 @@ /** - * Registry of discrete secret backends (memory + keychain today). + * Registry of discrete secret backends (memory + keychain + file). * * Each backend is independent — the same key name may exist in more than one. * Resolution is always (secret_store, secret_name). The {@link SecretStore} @@ -12,9 +12,10 @@ */ import type { SecretStore } from '../../core/secrets.js'; +import { MemorySecretStore } from '../../core/secrets.js'; /** Built-in writable registry backends. */ -export type SecretBackend = 'memory' | 'keychain'; +export type SecretBackend = 'memory' | 'keychain' | 'file'; /** Full secret_store vocabulary including env references. */ export type SecretStoreKind = SecretBackend | 'env'; @@ -22,6 +23,13 @@ export type SecretStoreKind = SecretBackend | 'env'; /** Default writable backend when store is omitted (backward compatible). */ export const DEFAULT_SECRET_BACKEND: SecretBackend = 'keychain'; +const WRITABLE_BACKENDS: readonly SecretBackend[] = ['memory', 'keychain', 'file'] as const; + +/** True when ``s`` is a daemon-owned writable store id (not env). */ +export function isWritableSecretBackend(s: string): s is SecretBackend { + return s === 'memory' || s === 'keychain' || s === 'file'; +} + /** * Maps store id → {@link SecretStore} implementation. * @@ -33,6 +41,8 @@ export class SecretStoreRegistry implements SecretStore { constructor( private readonly memory: SecretStore, private readonly keychain: SecretStore, + /** Defaults to in-memory stand-in so unit tests need not touch disk. */ + private readonly file: SecretStore = new MemorySecretStore(), ) {} /** Expose backends for tests and future registry iteration. */ @@ -44,17 +54,23 @@ export class SecretStoreRegistry implements SecretStore { return this.keychain; } + get fileStore(): SecretStore { + return this.file; + } + /** Built-in writable backends (order stable for ListSecrets). */ backends(): readonly SecretBackend[] { - return ['memory', 'keychain'] as const; + return WRITABLE_BACKENDS; } private storeFor(backend: SecretBackend): SecretStore { - return backend === 'memory' ? this.memory : this.keychain; + if (backend === 'memory') return this.memory; + if (backend === 'file') return this.file; + return this.keychain; } private requireBackend(backend: string): SecretBackend { - if (backend === 'memory' || backend === 'keychain') return backend; + if (isWritableSecretBackend(backend)) return backend; throw new Error(`Unknown secret store: ${backend}`); } @@ -103,7 +119,7 @@ export class SecretStoreRegistry implements SecretStore { /** * @deprecated Prefer {@link locateAll}. Returns the first backend that holds - * the key (memory before keychain), or null. + * the key (memory before keychain before file), or null. */ async locate(key: string): Promise { const all = await this.locateAll(key); @@ -124,12 +140,12 @@ export function requireNamespacedMemory( registry: SecretStoreRegistry | null, backend: string, ): { ok: true } | { ok: false; error: string } { - if (backend === 'memory' && !registry) { + if ((backend === 'memory' || backend === 'file') && !registry) { return { ok: false, error: - 'secret_store=memory requires a namespaced secret store (memory + keychain); ' + - 'this daemon is not running with SecretStoreRegistry', + `secret_store=${backend} requires a namespaced secret store ` + + '(memory + keychain + file); this daemon is not running with SecretStoreRegistry', }; } return { ok: true }; @@ -169,6 +185,9 @@ export function parseSecretStoreChoice( if (store === 'memory' || store === 'SECRET_STORE_MEMORY' || store === 3 || store === '3') { return { ok: true, backend: 'memory' }; } + if (store === 'file' || store === 'SECRET_STORE_FILE' || store === 4 || store === '4') { + return { ok: true, backend: 'file' }; + } if (store === 'env' || store === 'SECRET_STORE_ENV' || store === 2 || store === '2') { if (opts?.allowEnv) { return { ok: true, backend: 'env' }; @@ -185,7 +204,20 @@ export function parseSecretStoreChoice( /** Proto SecretStore enum numeric values for ListSecrets / SecretInfo. */ export function secretBackendToProto(backend: SecretBackend | null): number { - if (backend === 'memory') return 3; // SECRET_STORE_MEMORY if (backend === 'keychain') return 1; // SECRET_STORE_KEYCHAIN + if (backend === 'memory') return 3; // SECRET_STORE_MEMORY + if (backend === 'file') return 4; // SECRET_STORE_FILE return 0; // SECRET_STORE_UNSPECIFIED } + +/** + * Audit ``source`` label for a writable backend. + * Keychain keeps the unsuffixed base; memory/file append ``-memory`` / ``-file``. + */ +export function secretAuditSource( + base: 'http-secrets' | 'grpc-secrets' | 'http-configure' | 'grpc-configure', + backend: string, +): string { + if (backend === 'memory' || backend === 'file') return `${base}-${backend}`; + return base; +} diff --git a/packages/daemon/src/daemon/server/abbenay-service.test.ts b/packages/daemon/src/daemon/server/abbenay-service.test.ts index a936b67c..0371f1dd 100644 --- a/packages/daemon/src/daemon/server/abbenay-service.test.ts +++ b/packages/daemon/src/daemon/server/abbenay-service.test.ts @@ -408,11 +408,20 @@ describe('protoToConfigFile', () => { mem: { engine: 'openai', secret_name: 'M', secret_store: 3 }, envp: { engine: 'openai', secret_name: 'E', secret_store: 'SECRET_STORE_ENV' }, kc: { engine: 'openai', secret_name: 'K', secret_store: 'SECRET_STORE_KEYCHAIN' }, + filep: { engine: 'openai', secret_name: 'F', secret_store: 4 }, }, }); expect(config.providers!.mem.secret_store).toBe('memory'); expect(config.providers!.envp.secret_store).toBe('env'); expect(config.providers!.kc.secret_store).toBe('keychain'); + expect(config.providers!.filep.secret_store).toBe('file'); + + const proto = configFileToProto({ + providers: { + filep: { engine: 'openai', secret_name: 'F', secret_store: 'file' }, + }, + }); + expect(proto.providers!.filep.secret_store).toBe(4); }); it('provider with no models gets undefined models field', () => { @@ -1249,6 +1258,23 @@ describe('createAbbenayService handlers', () => { expect(kc.response?.exists).toBe(false); }); + it('GetKeyStatus checks file independently of keychain', async () => { + const { SecretStoreRegistry } = await import('../secrets/registry.js'); + const { MemorySecretStore } = await import('../../core/secrets.js'); + const memory = new MemorySecretStore(); + const keychain = new MemorySecretStore(); + const file = new MemorySecretStore(); + await file.set('FILE_ONLY', 'v'); + const registry = new SecretStoreRegistry(memory, keychain, file); + const state = createMockState({ secretStore: registry }); + const service = createServiceHandlers(state); + + const fileStatus = await invokeUnary(service.GetKeyStatus, { source: 'file', name: 'FILE_ONLY' }); + expect(fileStatus.response?.exists).toBe(true); + const kc = await invokeUnary(service.GetKeyStatus, { source: 'keychain', name: 'FILE_ONLY' }); + expect(kc.response?.exists).toBe(false); + }); + it('ListMcpServerConfigs merges config and runtime statuses', async () => { mockLoadConfig.mockReturnValue({ mcp_servers: { diff --git a/packages/daemon/src/daemon/server/abbenay-service.ts b/packages/daemon/src/daemon/server/abbenay-service.ts index b94a0d84..3e95c680 100644 --- a/packages/daemon/src/daemon/server/abbenay-service.ts +++ b/packages/daemon/src/daemon/server/abbenay-service.ts @@ -28,8 +28,10 @@ import { import { auditSecretChange } from '../../core/secrets.js'; import { isSecretStoreRegistry, + isWritableSecretBackend, parseSecretStoreChoice, requireNamespacedMemory, + secretAuditSource, secretBackendToProto, } from '../secrets/registry.js'; import { @@ -871,8 +873,7 @@ export function createAbbenayService( const write = registry ? registry.setIn(parsed.backend, key, value) : state.secretStore.set(key, value); - const auditSource = - parsed.backend === 'memory' ? 'grpc-secrets-memory' : 'grpc-secrets'; + const auditSource = secretAuditSource('grpc-secrets', parsed.backend); write.then(() => { auditSecretChange({ key, op: 'set', source: auditSource }); @@ -922,8 +923,7 @@ export function createAbbenayService( auditSecretChange({ key, op: 'delete', - source: - parsed.backend === 'memory' ? 'grpc-secrets-memory' : 'grpc-secrets', + source: secretAuditSource('grpc-secrets', parsed.backend), }); callback(null, {}); }).catch((error: unknown) => { @@ -1890,8 +1890,7 @@ export function createAbbenayService( } else { await state.secretStore.set(secretName, apiKey); } - const auditSource = - storeChoice.backend === 'memory' ? 'grpc-configure-memory' : 'grpc-configure'; + const auditSource = secretAuditSource('grpc-configure', storeChoice.backend); auditSecretChange({ key: secretName, op: 'set', source: auditSource }); config.providers[providerId].secret_name = secretName; config.providers[providerId].secret_store = storeChoice.backend; @@ -2045,11 +2044,14 @@ export function createAbbenayService( const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; - const backend = providerCfg.secret_store === 'memory' ? 'memory' : 'keychain'; + const backend = + providerCfg.secret_store === 'memory' || providerCfg.secret_store === 'file' + ? providerCfg.secret_store + : 'keychain'; if (registry) { await registry.deleteFrom(backend, secretName); } else if (backend === 'keychain') { - // Memory without a registry was never a valid write target. + // Memory/file without a registry was never a valid write target. await state.secretStore.delete(secretName); } auditSecretChange({ key: secretName, op: 'delete', source: 'grpc-configure' }); @@ -2108,7 +2110,7 @@ export function createAbbenayService( (async () => { try { let exists = false; - if (source === 'keychain' || source === 'memory') { + if (isWritableSecretBackend(source)) { const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; @@ -2366,17 +2368,20 @@ export function configFileToProto(config: ConfigFile): ConfigProto { } } const secretName = providerSecretName(pcfg); + let secretStoreProto = 0; + if (pcfg.secret_store === 'env') { + secretStoreProto = 2; + } else if (pcfg.secret_store === 'memory') { + secretStoreProto = 3; + } else if (pcfg.secret_store === 'file') { + secretStoreProto = 4; + } else if (secretName) { + secretStoreProto = 1; + } providers[pid] = { engine: pcfg.engine || pid, secret_name: secretName, - secret_store: - pcfg.secret_store === 'env' - ? 2 - : pcfg.secret_store === 'memory' - ? 3 - : secretName - ? 1 - : 0, + secret_store: secretStoreProto, api_key_keychain_name: pcfg.secret_store === 'env' ? undefined : secretName, api_key_env_var_name: pcfg.secret_store === 'env' @@ -2458,11 +2463,13 @@ export function protoToConfigFile(proto: ConfigProto): ConfigFile { } } const secretName = pcfg.secret_name || pcfg.api_key_keychain_name || undefined; - let secretStore: 'memory' | 'keychain' | 'env' | undefined; + let secretStore: 'memory' | 'keychain' | 'env' | 'file' | undefined; if (pcfg.secret_store === 2 || pcfg.secret_store === 'SECRET_STORE_ENV' || pcfg.secret_store === 'env') { secretStore = 'env'; } else if (pcfg.secret_store === 3 || pcfg.secret_store === 'SECRET_STORE_MEMORY' || pcfg.secret_store === 'memory') { secretStore = 'memory'; + } else if (pcfg.secret_store === 4 || pcfg.secret_store === 'SECRET_STORE_FILE' || pcfg.secret_store === 'file') { + secretStore = 'file'; } else if (pcfg.secret_store === 1 || pcfg.secret_store === 'SECRET_STORE_KEYCHAIN' || pcfg.secret_store === 'keychain') { secretStore = 'keychain'; } else if (pcfg.api_key_env_var_name && !secretName) { diff --git a/packages/daemon/src/daemon/state.ts b/packages/daemon/src/daemon/state.ts index 10ff623a..292b2321 100644 --- a/packages/daemon/src/daemon/state.ts +++ b/packages/daemon/src/daemon/state.ts @@ -12,6 +12,7 @@ import type { PolicyConfig } from '../core/policies.js'; import { MemorySecretStore } from '../core/secrets.js'; import { SecretStoreRegistry } from './secrets/registry.js'; import { KeychainSecretStore } from './secrets/keychain.js'; +import { FileSecretStore } from './secrets/file-store.js'; import { ToolRegistry } from '../core/tool-registry.js'; import { ToolRouter } from './tool-router.js'; import { McpClientPool } from './mcp-client-pool.js'; @@ -85,7 +86,11 @@ export class DaemonState extends CoreState { constructor() { super({ - secretStore: new SecretStoreRegistry(new MemorySecretStore(), new KeychainSecretStore()), + secretStore: new SecretStoreRegistry( + new MemorySecretStore(), + new KeychainSecretStore(), + new FileSecretStore(), + ), }); // Initialize tool registry, router, MCP client pool, and MCP server diff --git a/packages/daemon/src/daemon/web/api-schemas.test.ts b/packages/daemon/src/daemon/web/api-schemas.test.ts index 8ed02fe5..2da9b01a 100644 --- a/packages/daemon/src/daemon/web/api-schemas.test.ts +++ b/packages/daemon/src/daemon/web/api-schemas.test.ts @@ -74,7 +74,7 @@ describe('PostProviderConfigureBodySchema', () => { ).toBe(true); }); - it('accepts secretStore memory|keychain|env with apiKey or secretName', () => { + it('accepts secretStore memory|keychain|env|file with apiKey or secretName', () => { expect( PostProviderConfigureBodySchema.safeParse({ engine: 'openai', @@ -90,6 +90,13 @@ describe('PostProviderConfigureBodySchema', () => { secretStore: 'env', }).success, ).toBe(true); + expect( + PostProviderConfigureBodySchema.safeParse({ + engine: 'openai', + secretName: 'FILE_KEY', + secretStore: 'file', + }).success, + ).toBe(true); }); it('rejects apiKey together with envVarName', () => { @@ -174,7 +181,7 @@ describe('secret body schemas', () => { expect(PostSecretByKeyBodySchema.safeParse({ value: '' }).success).toBe(false); }); - it('accepts optional secretStore memory|keychain|env', () => { + it('accepts optional secretStore memory|keychain|env|file', () => { expect( PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secretStore: 'memory' }).success, ).toBe(true); @@ -184,6 +191,9 @@ describe('secret body schemas', () => { expect( PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secretStore: 'env' }).success, ).toBe(true); + expect( + PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secretStore: 'file' }).success, + ).toBe(true); expect( PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secretStore: 'vault' }).success, ).toBe(false); diff --git a/packages/daemon/src/daemon/web/api-schemas.ts b/packages/daemon/src/daemon/web/api-schemas.ts index ee1a2eee..4799504b 100644 --- a/packages/daemon/src/daemon/web/api-schemas.ts +++ b/packages/daemon/src/daemon/web/api-schemas.ts @@ -42,12 +42,15 @@ export const PostConfigBodySchema = z // ── Secrets ───────────────────────────────────────────────────────────── -const SecretStoreFieldSchema = z.enum(['memory', 'keychain', 'env']).optional(); +const SecretStoreFieldSchema = z.enum(['memory', 'keychain', 'env', 'file']).optional(); export const PostSecretByKeyBodySchema = z .object({ value: z.string().min(1), - /** Default: keychain (persistent). memory = process-lifetime only. */ + /** + * Writable store: memory | keychain (default) | file. + * `env` is accepted by the schema but rejected at the route (not writable). + */ secretStore: SecretStoreFieldSchema, }) .strict(); @@ -56,7 +59,10 @@ export const PostSecretBodySchema = z .object({ key: z.string().min(1), value: z.string().min(1), - /** Default: keychain (persistent). memory = process-lifetime only. */ + /** + * Writable store: memory | keychain (default) | file. + * `env` is accepted by the schema but rejected at the route (not writable). + */ secretStore: SecretStoreFieldSchema, }) .strict(); @@ -110,8 +116,12 @@ export const PostProviderConfigureBodySchema = z apiKey: z.string().min(1).optional(), /** Logical secret name (SetSecret key). Prefer over inventing from provider id. */ secretName: z.string().min(1).optional(), - /** Where to write apiKey: memory | keychain (default keychain). */ - secretStore: z.enum(['memory', 'keychain', 'env']).optional(), + /** + * Credential store: memory | keychain (default) | file | env. + * With apiKey: writable backends only (env rejected). Without apiKey: + * references an existing secret (`env` = process.env[secretName]). + */ + secretStore: z.enum(['memory', 'keychain', 'env', 'file']).optional(), /** @deprecated Use secretName. */ apiKeyKeychainName: z.string().min(1).optional(), envVarName: z.string().min(1).optional(), diff --git a/packages/daemon/src/daemon/web/server.ts b/packages/daemon/src/daemon/web/server.ts index 7680ce63..a6eb3793 100644 --- a/packages/daemon/src/daemon/web/server.ts +++ b/packages/daemon/src/daemon/web/server.ts @@ -18,7 +18,7 @@ import { fileURLToPath } from 'node:url'; import { getDefaultSocketPath } from '../transport.js'; import { loadConfig, saveConfig, loadWorkspaceConfig, saveWorkspaceConfig, getUserConfigPath, getWorkspaceConfigPath, providerSecretName, isProviderOwnedSecretName, type ConfigFile, type ProviderConfig } from '../../core/config.js'; import { auditSecretChange } from '../../core/secrets.js'; -import { isSecretStoreRegistry, parseSecretStoreChoice, requireNamespacedMemory } from '../secrets/registry.js'; +import { isSecretStoreRegistry, isWritableSecretBackend, parseSecretStoreChoice, requireNamespacedMemory, secretAuditSource } from '../secrets/registry.js'; import { auditProviderEndpointChange, auditProviderEndpointConfigDiff, @@ -1080,8 +1080,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): } else { await state.secretStore.set(key, parsed.data.value); } - const auditSource = - storeChoice.backend === 'memory' ? 'http-secrets-memory' : 'http-secrets'; + const auditSource = secretAuditSource('http-secrets', storeChoice.backend); auditSecretChange({ key, op: 'set', source: auditSource }); res.json({ success: true, secretStore: storeChoice.backend }); state.notifyModelsChanged('secret_updated'); @@ -1119,8 +1118,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): } else { await state.secretStore.set(parsed.data.key, parsed.data.value); } - const auditSource = - storeChoice.backend === 'memory' ? 'http-secrets-memory' : 'http-secrets'; + const auditSource = secretAuditSource('http-secrets', storeChoice.backend); auditSecretChange({ key: parsed.data.key, op: 'set', source: auditSource }); res.json({ success: true, secretStore: storeChoice.backend }); state.notifyModelsChanged('secret_updated'); @@ -1133,7 +1131,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): /** * DELETE /api/secrets/:key - Delete a secret - * Query: secretStore=memory|keychain (default keychain) + * Query: secretStore=memory|keychain|file (default keychain) */ app.delete('/api/secrets/:key', async (req, res) => { try { @@ -1170,8 +1168,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): auditSecretChange({ key: req.params.key, op: 'delete', - source: - storeChoice.backend === 'memory' ? 'http-secrets-memory' : 'http-secrets', + source: secretAuditSource('http-secrets', storeChoice.backend), }); res.json({ success: true, secretStore: storeChoice.backend }); state.notifyModelsChanged('secret_deleted'); @@ -1184,7 +1181,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): /** * GET /api/key-status - Check if a specific key is available - * Query: source=keychain|env, name=KEY_NAME + * Query: source=keychain|memory|file|env, name=KEY_NAME */ app.get('/api/key-status', async (req, res) => { try { @@ -1197,7 +1194,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): } let exists = false; - if (source === 'keychain' || source === 'memory') { + if (isWritableSecretBackend(source)) { const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; exists = registry ? await registry.hasIn(source, name) @@ -1491,8 +1488,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): } else { await state.secretStore.set(secretName, apiKey); } - const auditSource = - storeChoice.backend === 'memory' ? 'http-configure-memory' : 'http-configure'; + const auditSource = secretAuditSource('http-configure', storeChoice.backend); auditSecretChange({ key: secretName, op: 'set', source: auditSource }); config.providers[providerId].secret_name = secretName; config.providers[providerId].secret_store = storeChoice.backend; @@ -1505,7 +1501,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): config.providers[providerId].secret_store = 'env'; config.providers[providerId].api_key_env_var_name = secretNameRef; delete config.providers[providerId].api_key_keychain_name; - } else if (storeRaw === 'memory' || storeRaw === 'keychain') { + } else if (storeRaw === 'memory' || storeRaw === 'keychain' || storeRaw === 'file') { const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; const memoryOk = requireNamespacedMemory(registry, storeRaw); if (!memoryOk.ok) { @@ -1626,11 +1622,14 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; - const backend = providerCfg.secret_store === 'memory' ? 'memory' : 'keychain'; + const backend = + providerCfg.secret_store === 'memory' || providerCfg.secret_store === 'file' + ? providerCfg.secret_store + : 'keychain'; if (registry) { await registry.deleteFrom(backend, secretName); } else if (backend === 'keychain') { - // Memory without a registry was never a valid write target. + // Memory/file without a registry was never a valid write target. await state.secretStore.delete(secretName); } auditSecretChange({ key: secretName, op: 'delete', source: 'http-configure' }); diff --git a/packages/daemon/tests/integration/web-sse.test.ts b/packages/daemon/tests/integration/web-sse.test.ts index a87ddb3c..b75acfc9 100644 --- a/packages/daemon/tests/integration/web-sse.test.ts +++ b/packages/daemon/tests/integration/web-sse.test.ts @@ -63,6 +63,7 @@ const chatRequests: Array<{ model: string; messages: any[] }> = []; const mockSecretStore = new SecretStoreRegistry( new MemorySecretStore(), new MemorySecretStore(), + new MemorySecretStore(), // file stand-in (no disk I/O in this suite) ); function sleep(ms: number): Promise { @@ -558,6 +559,25 @@ describe('Web API - Secrets Endpoints', () => { expect(kc.body.exists).toBe(false); }); + it('GET /api/key-status?source=file reports file-only secrets', async () => { + await httpRequest('POST', `${baseUrl}/api/secrets/FILE_STATUS`, { + value: 'on-disk', + secretStore: 'file', + }); + const file = await httpRequest( + 'GET', + `${baseUrl}/api/key-status?source=file&name=FILE_STATUS`, + ); + expect(file.statusCode).toBe(200); + expect(file.body.exists).toBe(true); + const kc = await httpRequest( + 'GET', + `${baseUrl}/api/key-status?source=keychain&name=FILE_STATUS`, + ); + expect(kc.statusCode).toBe(200); + expect(kc.body.exists).toBe(false); + }); + it('POST /api/provider/:id/configure writes apiKey to memory backend', async () => { const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/mem-openai/configure`, { engine: 'openai', @@ -585,6 +605,35 @@ describe('Web API - Secrets Endpoints', () => { expect(body.success).toBe(true); }); + it('POST /api/provider/:id/configure picks an existing file secret', async () => { + await httpRequest('POST', `${baseUrl}/api/secrets/SHARED_FILE`, { + value: 'file-value', + secretStore: 'file', + }); + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/file-openai/configure`, { + engine: 'openai', + secretName: 'SHARED_FILE', + secretStore: 'file', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + expect(await mockSecretStore.hasIn('file', 'SHARED_FILE')).toBe(true); + expect(await mockSecretStore.hasIn('keychain', 'SHARED_FILE')).toBe(false); + }); + + it('POST /api/provider/:id/configure writes apiKey to file backend', async () => { + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/file-write/configure`, { + engine: 'openai', + apiKey: 'sk-file', + secretName: 'FILE_OPENAI_API_KEY', + secretStore: 'file', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + expect(await mockSecretStore.getFrom('file', 'FILE_OPENAI_API_KEY')).toBe('sk-file'); + expect(await mockSecretStore.hasIn('keychain', 'FILE_OPENAI_API_KEY')).toBe(false); + }); + it('POST /api/provider/:id/configure accepts secretStore=env without writing store', async () => { const before = await mockSecretStore.locateAll('ABBENAY_HTTP_ENV'); const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/env-openai/configure`, { diff --git a/packages/proto-ts/src/abbenay/v1/service.ts b/packages/proto-ts/src/abbenay/v1/service.ts index a69bcb11..7d0e289d 100644 --- a/packages/proto-ts/src/abbenay/v1/service.ts +++ b/packages/proto-ts/src/abbenay/v1/service.ts @@ -319,6 +319,8 @@ export enum SecretStore { SECRET_STORE_KEYCHAIN = 1, SECRET_STORE_ENV = 2, SECRET_STORE_MEMORY = 3, + /** SECRET_STORE_FILE - config-dir secrets.json (durable with volume) */ + SECRET_STORE_FILE = 4, UNRECOGNIZED = -1, } @@ -336,6 +338,9 @@ export function secretStoreFromJSON(object: any): SecretStore { case 3: case "SECRET_STORE_MEMORY": return SecretStore.SECRET_STORE_MEMORY; + case 4: + case "SECRET_STORE_FILE": + return SecretStore.SECRET_STORE_FILE; case -1: case "UNRECOGNIZED": default: @@ -353,6 +358,8 @@ export function secretStoreToJSON(object: SecretStore): string { return "SECRET_STORE_ENV"; case SecretStore.SECRET_STORE_MEMORY: return "SECRET_STORE_MEMORY"; + case SecretStore.SECRET_STORE_FILE: + return "SECRET_STORE_FILE"; case SecretStore.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py index 0b4896fb..f19b6708 100644 --- a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py +++ b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x61\x62\x62\x65nay/v1/service.proto\x12\nabbenay.v1\"\x07\n\x05\x45mpty\"+\n\tTimestamp\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\"C\n\x13ListMcpToolsRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_server_filter\":\n\x14ListMcpToolsResponse\x12\"\n\x05tools\x18\x01 \x03(\x0b\x32\x13.abbenay.v1.McpTool\"o\n\x07McpTool\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x19\n\x11input_schema_json\x18\x03 \x01(\t\x12\x11\n\tserver_id\x18\x04 \x01(\t\x12\x13\n\x0b\x61nnotations\x18\x05 \x03(\t\"\xcf\x01\n\x0f\x43\x61llToolRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x02 \x01(\t\x12\x17\n\nrequest_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x39\n\x07\x63ontext\x18\x04 \x03(\x0b\x32(.abbenay.v1.CallToolRequest.ContextEntry\x1a.\n\x0c\x43ontextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b_request_id\"\xc9\x02\n\x11ToolResponseChunk\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12,\n\x08progress\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.ToolProgressH\x00\x12/\n\ntext_delta\x18\x03 \x01(\x0b\x32\x19.abbenay.v1.ToolTextDeltaH\x00\x12\x30\n\x07partial\x18\x04 \x01(\x0b\x32\x1d.abbenay.v1.ToolPartialResultH\x00\x12\x33\n\x0c\x66inal_result\x18\x05 \x01(\x0b\x32\x1b.abbenay.v1.ToolFinalResultH\x00\x12&\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x15.abbenay.v1.ToolErrorH\x00\x12(\n\x06prompt\x18\x07 \x01(\x0b\x32\x16.abbenay.v1.ToolPromptH\x00\x42\x08\n\x06result\"]\n\x0cToolProgress\x12\x12\n\npercentage\x18\x01 \x01(\x02\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x18\n\x0b\x65ta_seconds\x18\x03 \x01(\x03H\x00\x88\x01\x01\x42\x0e\n\x0c_eta_seconds\"\x1d\n\rToolTextDelta\x12\x0c\n\x04text\x18\x01 \x01(\t\";\n\x11ToolPartialResult\x12\x14\n\x0c\x63ontent_json\x18\x01 \x01(\t\x12\x10\n\x08is_final\x18\x02 \x01(\x08\"M\n\x0fToolFinalResult\x12(\n\x07\x63ontent\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.ToolContent\x12\x10\n\x08is_error\x18\x02 \x01(\x08\"W\n\x0bToolContent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x11\n\tmime_type\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0b\n\x03uri\x18\x05 \x01(\t\"V\n\tToolError\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x19\n\x0c\x64\x65tails_json\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0f\n\r_details_json\"o\n\nToolPrompt\x12\x11\n\tprompt_id\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x0f\n\x07options\x18\x03 \x03(\t\x12\x1a\n\rdefault_value\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_default_value\"\xae\x01\n\x16ToolInteractiveRequest\x12+\n\x04\x63\x61ll\x18\x01 \x01(\x0b\x32\x1b.abbenay.v1.CallToolRequestH\x00\x12\x32\n\x08response\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.ToolPromptResponseH\x00\x12(\n\x06\x63\x61ncel\x18\x03 \x01(\x0b\x32\x16.abbenay.v1.ToolCancelH\x00\x42\t\n\x07request\"9\n\x12ToolPromptResponse\x12\x11\n\tprompt_id\x18\x01 \x01(\t\x12\x10\n\x08response\x18\x02 \x01(\t\"0\n\nToolCancel\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\"Z\n\x15\x43\x61llToolsBatchRequest\x12*\n\x05\x63\x61lls\x18\x01 \x03(\x0b\x32\x1b.abbenay.v1.CallToolRequest\x12\x15\n\rstop_on_error\x18\x02 \x01(\x08\"l\n\x14ListResourcesRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nuri_prefix\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x10\n\x0e_server_filterB\r\n\x0b_uri_prefix\"C\n\x15ListResourcesResponse\x12*\n\tresources\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.McpResource\"P\n\x0bMcpResource\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x11\n\tmime_type\x18\x04 \x01(\t\"\"\n\x13ReadResourceRequest\x12\x0b\n\x03uri\x18\x01 \x01(\t\"A\n\x14ReadResourceResponse\x12)\n\x08\x63ontents\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.ToolContent\"B\n\x12ListPromptsRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_server_filter\"=\n\x13ListPromptsResponse\x12&\n\x07prompts\x18\x01 \x03(\x0b\x32\x15.abbenay.v1.McpPrompt\"`\n\tMcpPrompt\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x30\n\targuments\x18\x03 \x03(\x0b\x32\x1d.abbenay.v1.McpPromptArgument\"H\n\x11McpPromptArgument\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x10\n\x08required\x18\x03 \x01(\x08\"\x92\x01\n\x10GetPromptRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12>\n\targuments\x18\x02 \x03(\x0b\x32+.abbenay.v1.GetPromptRequest.ArgumentsEntry\x1a\x30\n\x0e\x41rgumentsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"U\n\x11GetPromptResponse\x12\x13\n\x0b\x64\x65scription\x18\x01 \x01(\t\x12+\n\x08messages\x18\x02 \x03(\x0b\x32\x19.abbenay.v1.PromptMessage\".\n\rPromptMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\"\xc8\x01\n\x0b\x43hatRequest\x12\r\n\x05model\x18\x01 \x01(\t\x12%\n\x08messages\x18\x02 \x03(\x0b\x32\x13.abbenay.v1.Message\x12(\n\x07options\x18\x03 \x01(\x0b\x32\x17.abbenay.v1.ChatOptions\x12\x1f\n\x05tools\x18\x04 \x03(\x0b\x32\x10.abbenay.v1.Tool\x12-\n\x06policy\x18\x05 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfigH\x00\x88\x01\x01\x42\t\n\x07_policy\"\xb2\x01\n\x12SessionChatRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12$\n\x07message\x18\x02 \x01(\x0b\x32\x13.abbenay.v1.Message\x12(\n\x07options\x18\x03 \x01(\x0b\x32\x17.abbenay.v1.ChatOptions\x12-\n\x06policy\x18\x04 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfigH\x00\x88\x01\x01\x42\t\n\x07_policy\"\xb9\x02\n\x0b\x43hatOptions\x12\x18\n\x0btemperature\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x12\n\x05top_p\x18\x03 \x01(\x02H\x02\x88\x01\x01\x12\x0c\n\x04stop\x18\x04 \x03(\t\x12\x14\n\x0c\x65nable_tools\x18\x05 \x01(\x08\x12\x1b\n\x13max_tool_iterations\x18\x06 \x01(\x05\x12\x13\n\x0btool_filter\x18\x07 \x03(\t\x12\x12\n\x05top_k\x18\x08 \x01(\x05H\x03\x88\x01\x01\x12\x14\n\x07timeout\x18\t \x01(\x05H\x04\x88\x01\x01\x12\x16\n\ttool_mode\x18\n \x01(\tH\x05\x88\x01\x01\x42\x0e\n\x0c_temperatureB\r\n\x0b_max_tokensB\x08\n\x06_top_pB\x08\n\x06_top_kB\n\n\x08_timeoutB\x0c\n\n_tool_mode\"\xc3\x02\n\tChatChunk\x12%\n\x04text\x18\x01 \x01(\x0b\x32\x15.abbenay.v1.TextChunkH\x00\x12.\n\ttool_call\x18\x02 \x01(\x0b\x32\x19.abbenay.v1.ToolCallChunkH\x00\x12\x32\n\x0btool_result\x18\x03 \x01(\x0b\x32\x1b.abbenay.v1.ToolResultChunkH\x00\x12)\n\x06prompt\x18\x04 \x01(\x0b\x32\x17.abbenay.v1.PromptChunkH\x00\x12\'\n\x05usage\x18\x05 \x01(\x0b\x32\x16.abbenay.v1.UsageChunkH\x00\x12\'\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x16.abbenay.v1.ErrorChunkH\x00\x12%\n\x04\x64one\x18\x07 \x01(\x0b\x32\x15.abbenay.v1.DoneChunkH\x00\x42\x07\n\x05\x63hunk\"\x19\n\tTextChunk\x12\x0c\n\x04text\x18\x01 \x01(\t\"<\n\rToolCallChunk\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\targuments\x18\x03 \x01(\t\"X\n\x0fToolResultChunk\x12\x14\n\x0ctool_call_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\t\x12\x10\n\x08is_error\x18\x04 \x01(\x08\"\\\n\x0bPromptChunk\x12\x14\n\x0ctool_call_id\x18\x01 \x01(\t\x12\x11\n\ttool_name\x18\x02 \x01(\t\x12\x13\n\x0bprompt_text\x18\x03 \x01(\t\x12\x0f\n\x07options\x18\x04 \x03(\t\"T\n\nUsageChunk\x12\x15\n\rprompt_tokens\x18\x01 \x01(\x05\x12\x19\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x12\x14\n\x0ctotal_tokens\x18\x03 \x01(\x05\"+\n\nErrorChunk\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\"\"\n\tDoneChunk\x12\x15\n\rfinish_reason\x18\x01 \x01(\t\"\x88\x01\n\x07Message\x12\x1e\n\x04role\x18\x01 \x01(\x0e\x32\x10.abbenay.v1.Role\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12(\n\ntool_calls\x18\x03 \x03(\x0b\x32\x14.abbenay.v1.ToolCall\x12\x14\n\x0ctool_call_id\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\"7\n\x08ToolCall\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\targuments\x18\x03 \x01(\t\"\xc4\x03\n\x07Session\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\x12\r\n\x05topic\x18\x03 \x01(\t\x12%\n\x08messages\x18\x04 \x03(\x0b\x32\x13.abbenay.v1.Message\x12*\n\ncreated_by\x18\x05 \x01(\x0b\x32\x16.abbenay.v1.ClientInfo\x12)\n\ncreated_at\x18\x06 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12)\n\nupdated_at\x18\x07 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12\x33\n\x08metadata\x18\x08 \x03(\x0b\x32!.abbenay.v1.Session.MetadataEntry\x12\x18\n\x0b\x66orked_from\x18\t \x01(\tH\x00\x88\x01\x01\x12\x17\n\nfork_point\x18\n \x01(\x05H\x01\x88\x01\x01\x12\x1b\n\x0e\x63\x61\x63hed_summary\x18\x0b \x01(\tH\x02\x88\x01\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0c_forked_fromB\r\n\x0b_fork_pointB\x11\n\x0f_cached_summary\"Z\n\nClientInfo\x12+\n\x0b\x63lient_type\x18\x01 \x01(\x0e\x32\x16.abbenay.v1.ClientType\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x0c\n\x04user\x18\x03 \x01(\t\"\xb6\x01\n\x14\x43reateSessionRequest\x12\r\n\x05model\x18\x01 \x01(\t\x12\x12\n\x05topic\x18\x02 \x01(\tH\x00\x88\x01\x01\x12@\n\x08metadata\x18\x03 \x03(\x0b\x32..abbenay.v1.CreateSessionRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x08\n\x06_topic\"A\n\x11GetSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x18\n\x10include_messages\x18\x02 \x01(\x08\"\x8e\x01\n\x13ListSessionsRequest\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x1a\n\rsource_filter\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cmodel_filter\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\x10\n\x0e_source_filterB\x0f\n\r_model_filter\"Y\n\x14ListSessionsResponse\x12,\n\x08sessions\x18\x01 \x03(\x0b\x32\x1a.abbenay.v1.SessionSummary\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\xcf\x01\n\x0eSessionSummary\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\x12\r\n\x05topic\x18\x03 \x01(\t\x12\x15\n\rmessage_count\x18\x04 \x01(\x05\x12&\n\x06source\x18\x05 \x01(\x0e\x32\x16.abbenay.v1.ClientType\x12)\n\ncreated_at\x18\x06 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12)\n\nupdated_at\x18\x07 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\"*\n\x14\x44\x65leteSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\"\x16\n\x14WatchSessionsRequest\"\xb8\x01\n\x0cSessionEvent\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12-\n\x07\x63reated\x18\x02 \x01(\x0b\x32\x1a.abbenay.v1.SessionCreatedH\x00\x12-\n\x07updated\x18\x03 \x01(\x0b\x32\x1a.abbenay.v1.SessionUpdatedH\x00\x12-\n\x07\x64\x65leted\x18\x04 \x01(\x0b\x32\x1a.abbenay.v1.SessionDeletedH\x00\x42\x07\n\x05\x65vent\"=\n\x0eSessionCreated\x12+\n\x07session\x18\x01 \x01(\x0b\x32\x1a.abbenay.v1.SessionSummary\"X\n\x0eSessionUpdated\x12+\n\x07session\x18\x01 \x01(\x0b\x32\x1a.abbenay.v1.SessionSummary\x12\x19\n\x11new_message_count\x18\x02 \x01(\x05\"\x10\n\x0eSessionDeleted\"\x80\x01\n\x14ReplaySessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12(\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x18.abbenay.v1.ReplayFormat\x12\x19\n\x0cmax_messages\x18\x03 \x01(\x05H\x00\x88\x01\x01\x42\x0f\n\r_max_messages\"a\n\x15ReplaySessionResponse\x12\x19\n\x11\x66ormatted_content\x18\x01 \x01(\t\x12\x15\n\rmessage_count\x18\x02 \x01(\x05\x12\x16\n\x0etoken_estimate\x18\x03 \x01(\x05\"_\n\x17SummarizeSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1c\n\x0fsummarize_model\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x12\n\x10_summarize_model\"?\n\x18SummarizeSessionResponse\x12\x0f\n\x07summary\x18\x01 \x01(\t\x12\x12\n\nfrom_cache\x18\x02 \x01(\x08\"v\n\x12\x46orkSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x16\n\tnew_model\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nfork_point\x18\x03 \x01(\x05H\x01\x88\x01\x01\x42\x0c\n\n_new_modelB\r\n\x0b_fork_point\"b\n\x14\x45xportSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1c\n\x14include_tool_results\x18\x02 \x01(\x08\x12\x18\n\x10include_metadata\x18\x03 \x01(\x08\"-\n\x15\x45xportSessionResponse\x12\x14\n\x0cjson_content\x18\x01 \x01(\t\"E\n\x14ImportSessionRequest\x12\x14\n\x0cjson_content\x18\x01 \x01(\t\x12\x17\n\x0fgenerate_new_id\x18\x02 \x01(\x08\"^\n\x11ListModelsRequest\x12\x1c\n\x0fprovider_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\x0fworkspace_paths\x18\x02 \x03(\tB\x12\n\x10_provider_filter\"7\n\x12ListModelsResponse\x12!\n\x06models\x18\x01 \x03(\x0b\x32\x11.abbenay.v1.Model\"\x9a\x01\n\x15\x44iscoverModelsRequest\x12\x11\n\tengine_id\x18\x01 \x01(\t\x12\x14\n\x07\x61pi_key\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08\x62\x61se_url\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bprovider_id\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\n\n\x08_api_keyB\x0b\n\t_base_urlB\x0e\n\x0c_provider_id\";\n\x16\x44iscoverModelsResponse\x12!\n\x06models\x18\x01 \x03(\x0b\x32\x11.abbenay.v1.Model\"\x93\x02\n\x05Model\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08provider\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x33\n\x0c\x63\x61pabilities\x18\x04 \x01(\x0b\x32\x1d.abbenay.v1.ModelCapabilities\x12\'\n\x06source\x18\x05 \x01(\x0e\x32\x17.abbenay.v1.ModelSource\x12\x0e\n\x06\x65ngine\x18\x06 \x01(\t\x12,\n\x06params\x18\x07 \x01(\x0b\x32\x17.abbenay.v1.ModelParamsH\x00\x88\x01\x01\x12\x17\n\x0f\x65ngine_model_id\x18\x08 \x01(\t\x12\x13\n\x06policy\x18\t \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_paramsB\t\n\x07_policy\"\xa3\x02\n\x0bModelParams\x12\x18\n\x0btemperature\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12\x12\n\x05top_p\x18\x02 \x01(\x02H\x01\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x03 \x01(\x05H\x02\x88\x01\x01\x12\x1a\n\rsystem_prompt\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x1f\n\x12system_prompt_mode\x18\x05 \x01(\tH\x04\x88\x01\x01\x12\x12\n\x05top_k\x18\x06 \x01(\x05H\x05\x88\x01\x01\x12\x14\n\x07timeout\x18\x07 \x01(\x05H\x06\x88\x01\x01\x42\x0e\n\x0c_temperatureB\x08\n\x06_top_pB\r\n\x0b_max_tokensB\x10\n\x0e_system_promptB\x15\n\x13_system_prompt_modeB\x08\n\x06_top_kB\n\n\x08_timeout\"\x90\x01\n\x11ModelCapabilities\x12\x1a\n\x12supports_streaming\x18\x01 \x01(\x08\x12\x16\n\x0esupports_tools\x18\x02 \x01(\x08\x12\x17\n\x0fsupports_vision\x18\x03 \x01(\x08\x12\x1b\n\x0e\x63ontext_window\x18\x04 \x01(\x05H\x00\x88\x01\x01\x42\x11\n\x0f_context_window\"/\n\x14ListProvidersRequest\x12\x17\n\x0fworkspace_paths\x18\x01 \x03(\t\"@\n\x15ListProvidersResponse\x12\'\n\tproviders\x18\x01 \x03(\x0b\x32\x14.abbenay.v1.Provider\"\xea\x01\n\x08Provider\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nconfigured\x18\x02 \x01(\x08\x12\x0f\n\x07healthy\x18\x03 \x01(\x08\x12/\n\rprovider_type\x18\x04 \x01(\x0e\x32\x18.abbenay.v1.ProviderType\x12\x14\n\x0crequires_key\x18\x05 \x01(\x08\x12\x1d\n\x10\x64\x65\x66\x61ult_base_url\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06\x65ngine\x18\x07 \x01(\t\x12\x15\n\x08\x62\x61se_url\x18\x08 \x01(\tH\x01\x88\x01\x01\x42\x13\n\x11_default_base_urlB\x0b\n\t_base_url\"/\n\x18GetProviderStatusRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\"\x93\x01\n\x0eProviderStatus\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x12\n\nconfigured\x18\x02 \x01(\x08\x12\x0f\n\x07healthy\x18\x03 \x01(\x08\x12\x12\n\x05\x65rror\x18\x04 \x01(\tH\x00\x88\x01\x01\x12)\n\nlast_check\x18\x05 \x01(\x0b\x32\x15.abbenay.v1.TimestampB\x08\n\x06_error\"\x14\n\x12ListEnginesRequest\":\n\x13ListEnginesResponse\x12#\n\x07\x65ngines\x18\x01 \x03(\x0b\x32\x12.abbenay.v1.Engine\"\xbc\x01\n\x06\x45ngine\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0crequires_key\x18\x02 \x01(\x08\x12\x1d\n\x10\x64\x65\x66\x61ult_base_url\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x1c\n\x0f\x64\x65\x66\x61ult_env_var\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x19\n\x0c\x64isplay_name\x18\x05 \x01(\tH\x02\x88\x01\x01\x42\x13\n\x11_default_base_urlB\x12\n\x10_default_env_varB\x0f\n\r_display_name\"@\n\x10ListToolsRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_server_filter\"4\n\x11ListToolsResponse\x12\x1f\n\x05tools\x18\x01 \x03(\x0b\x32\x10.abbenay.v1.Tool\"O\n\x04Tool\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cinput_schema\x18\x03 \x01(\t\x12\x0e\n\x06server\x18\x04 \x01(\t\"5\n\x12\x45xecuteToolRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\targuments\x18\x02 \x01(\t\"8\n\x13\x45xecuteToolResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x10\n\x08is_error\x18\x02 \x01(\x08\"6\n\x10GetConfigRequest\x12\x15\n\x08location\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_location\"E\n\x11GetConfigResponse\x12\"\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x12.abbenay.v1.Config\x12\x0c\n\x04path\x18\x02 \x01(\t\"\xd9\x03\n\x06\x43onfig\x12\x34\n\tproviders\x18\x01 \x03(\x0b\x32!.abbenay.v1.Config.ProvidersEntry\x12\x37\n\x0bmcp_servers\x18\x02 \x03(\x0b\x32\".abbenay.v1.Config.McpServersEntry\x12\x34\n\x0btool_policy\x18\x03 \x01(\x0b\x32\x1f.abbenay.v1.ToolPolicyConfigMsg\x12\x34\n\tconsumers\x18\x04 \x03(\x0b\x32!.abbenay.v1.Config.ConsumersEntry\x1aP\n\x0eProvidersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12-\n\x05value\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.FullProviderConfig:\x02\x38\x01\x1aQ\n\x0fMcpServersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12-\n\x05value\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.McpServerConfigMsg:\x02\x38\x01\x1aO\n\x0e\x43onsumersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x1d.abbenay.v1.ConsumerConfigMsg:\x02\x38\x01\"\xba\x03\n\x12\x46ullProviderConfig\x12\x0e\n\x06\x65ngine\x18\x01 \x01(\t\x12\"\n\x15\x61pi_key_keychain_name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12!\n\x14\x61pi_key_env_var_name\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08\x62\x61se_url\x18\x04 \x01(\tH\x02\x88\x01\x01\x12:\n\x06models\x18\x05 \x03(\x0b\x32*.abbenay.v1.FullProviderConfig.ModelsEntry\x12\x18\n\x0bsecret_name\x18\x06 \x01(\tH\x03\x88\x01\x01\x12\x32\n\x0csecret_store\x18\x07 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x04\x88\x01\x01\x1aK\n\x0bModelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05value\x18\x02 \x01(\x0b\x32\x1c.abbenay.v1.ModelParamConfig:\x02\x38\x01\x42\x18\n\x16_api_key_keychain_nameB\x17\n\x15_api_key_env_var_nameB\x0b\n\t_base_urlB\x0e\n\x0c_secret_nameB\x0f\n\r_secret_store\"\xec\x02\n\x10ModelParamConfig\x12\x15\n\x08model_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06policy\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x1a\n\rsystem_prompt\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x1f\n\x12system_prompt_mode\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0btemperature\x18\x05 \x01(\x02H\x04\x88\x01\x01\x12\x12\n\x05top_p\x18\x06 \x01(\x02H\x05\x88\x01\x01\x12\x12\n\x05top_k\x18\x07 \x01(\x05H\x06\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x08 \x01(\x05H\x07\x88\x01\x01\x12\x14\n\x07timeout\x18\t \x01(\x05H\x08\x88\x01\x01\x42\x0b\n\t_model_idB\t\n\x07_policyB\x10\n\x0e_system_promptB\x15\n\x13_system_prompt_modeB\x0e\n\x0c_temperatureB\x08\n\x06_top_pB\x08\n\x06_top_kB\r\n\x0b_max_tokensB\n\n\x08_timeout\"\x88\x03\n\x12McpServerConfigMsg\x12\x14\n\x07\x63ommand\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x03(\t\x12\x10\n\x03url\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x11\n\ttransport\x18\x04 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x05 \x01(\x08\x12<\n\x07headers\x18\x06 \x03(\x0b\x32+.abbenay.v1.McpServerConfigMsg.HeadersEntry\x12\x34\n\x03\x65nv\x18\x07 \x03(\x0b\x32\'.abbenay.v1.McpServerConfigMsg.EnvEntry\x12\x1e\n\x11max_response_size\x18\x08 \x01(\x05H\x02\x88\x01\x01\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\n\n\x08_commandB\x06\n\x04_urlB\x14\n\x12_max_response_size\"\x86\x02\n\x13ToolPolicyConfigMsg\x12 \n\x13max_tool_iterations\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x61uto_approve\x18\x02 \x03(\t\x12\x18\n\x10require_approval\x18\x03 \x03(\t\x12\x16\n\x0e\x64isabled_tools\x18\x04 \x03(\t\x12=\n\x07\x61liases\x18\x05 \x03(\x0b\x32,.abbenay.v1.ToolPolicyConfigMsg.AliasesEntry\x1a.\n\x0c\x41liasesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x16\n\x14_max_tool_iterations\"\xa4\x01\n\x11\x43onsumerConfigMsg\x12\x16\n\ttoken_env\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x1b\n\x0etoken_keychain\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x39\n\x0c\x63\x61pabilities\x18\x03 \x01(\x0b\x32#.abbenay.v1.ConsumerCapabilitiesMsgB\x0c\n\n_token_envB\x11\n\x0f_token_keychain\"\x9b\x02\n\x17\x43onsumerCapabilitiesMsg\x12\x1a\n\rinline_policy\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x19\n\x0cmcp_register\x18\x02 \x01(\x08H\x01\x88\x01\x01\x12\x14\n\x07secrets\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12\x13\n\x06\x63onfig\x18\x04 \x01(\x08H\x03\x88\x01\x01\x12\x16\n\tproviders\x18\x05 \x01(\x08H\x04\x88\x01\x01\x12\x15\n\x08shutdown\x18\x06 \x01(\x08H\x05\x88\x01\x01\x12\x11\n\x04\x63hat\x18\x07 \x01(\x08H\x06\x88\x01\x01\x42\x10\n\x0e_inline_policyB\x0f\n\r_mcp_registerB\n\n\x08_secretsB\t\n\x07_configB\x0c\n\n_providersB\x0b\n\t_shutdownB\x07\n\x05_chat\"]\n\x13UpdateConfigRequest\x12\"\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x12.abbenay.v1.Config\x12\x15\n\x08location\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_location\"\x80\x03\n\x18\x43onfigureProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x13\n\x06\x65ngine\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x61pi_key\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x19\n\x0c\x65nv_var_name\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x15\n\x08\x62\x61se_url\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x13\n\x06target\x18\x06 \x01(\tH\x04\x88\x01\x01\x12\x1b\n\x0eworkspace_path\x18\x07 \x01(\tH\x05\x88\x01\x01\x12\x18\n\x0bsecret_name\x18\x08 \x01(\tH\x06\x88\x01\x01\x12\x32\n\x0csecret_store\x18\t \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x07\x88\x01\x01\x42\t\n\x07_engineB\n\n\x08_api_keyB\x0f\n\r_env_var_nameB\x0b\n\t_base_urlB\t\n\x07_targetB\x11\n\x0f_workspace_pathB\x0e\n\x0c_secret_nameB\x0f\n\r_secret_store\",\n\x19\x43onfigureProviderResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"|\n\x15RemoveProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x13\n\x06target\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x1b\n\x0eworkspace_path\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_targetB\x11\n\x0f_workspace_path\"\x1d\n\x1bGetProviderTemplatesRequest\"O\n\x1cGetProviderTemplatesResponse\x12/\n\ttemplates\x18\x01 \x03(\x0b\x32\x1c.abbenay.v1.ProviderTemplate\"\x84\x01\n\x10ProviderTemplate\x12\x0e\n\x06\x65ngine\x18\x01 \x01(\t\x12\x16\n\x0esuggested_name\x18\x02 \x01(\t\x12\x1d\n\x10\x64\x65\x66\x61ult_base_url\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0crequires_key\x18\x04 \x01(\x08\x42\x13\n\x11_default_base_url\"3\n\x13GetKeyStatusRequest\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"&\n\x14GetKeyStatusResponse\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\"\x1d\n\x1bListMcpServerConfigsRequest\"U\n\x1cListMcpServerConfigsResponse\x12\x35\n\x0bmcp_servers\x18\x01 \x03(\x0b\x32 .abbenay.v1.McpServerStatusEntry\"\x88\x01\n\x14McpServerStatusEntry\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x12\x0e\n\x06status\x18\x04 \x01(\t\x12\x12\n\ntool_count\x18\x05 \x01(\x05\x12\x12\n\x05\x65rror\x18\x06 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\".\n\x19ReconnectMcpServerRequest\x12\x11\n\tserver_id\x18\x01 \x01(\t\"M\n\x13\x43reatePolicyRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12(\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfig\"#\n\x13\x44\x65letePolicyRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"V\n\x10GetSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05store\x18\x02 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x00\x88\x01\x01\x42\x08\n\x06_store\"\"\n\x11GetSecretResponse\x12\r\n\x05value\x18\x01 \x01(\t\"e\n\x10SetSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x12+\n\x05store\x18\x03 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x00\x88\x01\x01\x42\x08\n\x06_store\"Y\n\x13\x44\x65leteSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05store\x18\x02 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x00\x88\x01\x01\x42\x08\n\x06_store\"\x14\n\x12ListSecretsRequest\">\n\x13ListSecretsResponse\x12\'\n\x07secrets\x18\x01 \x03(\x0b\x32\x16.abbenay.v1.SecretInfo\"T\n\nSecretInfo\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05store\x18\x02 \x01(\x0e\x32\x17.abbenay.v1.SecretStore\x12\x11\n\thas_value\x18\x03 \x01(\x08\"~\n\x0fRegisterRequest\x12&\n\x06\x63lient\x18\x01 \x01(\x0b\x32\x16.abbenay.v1.ClientInfo\x12\x12\n\nis_spawner\x18\x02 \x01(\x08\x12\x16\n\x0eworkspace_path\x18\x03 \x01(\t\x12\x17\n\x0fworkspace_paths\x18\x04 \x03(\t\"@\n\x10RegisterResponse\x12\x11\n\tclient_id\x18\x01 \x01(\t\x12\x19\n\x11\x63onnected_clients\x18\x02 \x01(\x05\"&\n\x11UnregisterRequest\x12\x11\n\tclient_id\x18\x01 \x01(\t\"\x12\n\x10GetStatusRequest\"\x1f\n\x1dGetConnectedWorkspacesRequest\"4\n\x1eGetConnectedWorkspacesResponse\x12\x12\n\nworkspaces\x18\x01 \x03(\t\"\xcc\x01\n\x0c\x44\x61\x65monStatus\x12\x0f\n\x07version\x18\x01 \x01(\t\x12)\n\nstarted_at\x18\x02 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12\x19\n\x11\x63onnected_clients\x18\x03 \x01(\x05\x12\x17\n\x0f\x61\x63tive_sessions\x18\x04 \x01(\x05\x12,\n\x07\x63lients\x18\x05 \x03(\x0b\x32\x1b.abbenay.v1.ConnectedClient\x12\x1e\n\x16registered_mcp_servers\x18\x06 \x03(\t\"\xaa\x01\n\x0f\x43onnectedClient\x12\x11\n\tclient_id\x18\x01 \x01(\t\x12+\n\x0b\x63lient_type\x18\x02 \x01(\x0e\x32\x16.abbenay.v1.ClientType\x12+\n\x0c\x63onnected_at\x18\x03 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12\x12\n\nis_spawner\x18\x04 \x01(\x08\x12\x16\n\x0eworkspace_path\x18\x05 \x01(\t\">\n\x0fShutdownRequest\x12\r\n\x05\x66orce\x18\x01 \x01(\x08\x12\x1c\n\x14grace_period_seconds\x18\x02 \x01(\x05\"\x14\n\x12HealthCheckRequest\"7\n\x13HealthCheckResponse\x12\x0f\n\x07healthy\x18\x01 \x01(\x08\x12\x0f\n\x07version\x18\x02 \x01(\t\"%\n\x15StartWebServerRequest\x12\x0c\n\x04port\x18\x01 \x01(\x05\"]\n\x16StartWebServerResponse\x12\x0f\n\x07started\x18\x01 \x01(\x08\x12\x17\n\x0f\x61lready_running\x18\x02 \x01(\x08\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\x0b\n\x03url\x18\x04 \x01(\t\"\x16\n\x14StopWebServerRequest\"\x8d\x03\n\rVSCodeRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x34\n\x0binvoke_tool\x18\x02 \x01(\x0b\x32\x1d.abbenay.v1.InvokeToolRequestH\x00\x12:\n\x0blist_models\x18\x03 \x01(\x0b\x32#.abbenay.v1.ListVSCodeModelsRequestH\x00\x12\x36\n\tsend_chat\x18\x04 \x01(\x0b\x32!.abbenay.v1.SendVSCodeChatRequestH\x00\x12\x38\n\rget_workspace\x18\x06 \x01(\x0b\x32\x1f.abbenay.v1.GetWorkspaceRequestH\x00\x12?\n\x0emodels_changed\x18\x07 \x01(\x0b\x32%.abbenay.v1.ModelsChangedNotificationH\x00\x12\x38\n\nlist_tools\x18\x08 \x01(\x0b\x32\".abbenay.v1.ListVSCodeToolsRequestH\x00\x42\t\n\x07request\"+\n\x19ModelsChangedNotification\x12\x0e\n\x06reason\x18\x01 \x01(\t\"\xbe\x03\n\x0eVSCodeResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x35\n\x0binvoke_tool\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.InvokeToolResponseH\x00\x12;\n\x0blist_models\x18\x03 \x01(\x0b\x32$.abbenay.v1.ListVSCodeModelsResponseH\x00\x12\x37\n\tsend_chat\x18\x04 \x01(\x0b\x32\".abbenay.v1.SendVSCodeChatResponseH\x00\x12(\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x17.abbenay.v1.VSCodeErrorH\x00\x12\x39\n\rget_workspace\x18\x07 \x01(\x0b\x32 .abbenay.v1.GetWorkspaceResponseH\x00\x12\x39\n\nlist_tools\x18\x08 \x01(\x0b\x32#.abbenay.v1.ListVSCodeToolsResponseH\x00\x12?\n\x0eregister_tools\x18\t \x01(\x0b\x32%.abbenay.v1.RegisterToolsNotificationH\x00\x42\n\n\x08response\"\x15\n\x13GetWorkspaceRequest\"I\n\x14GetWorkspaceResponse\x12\x16\n\x0eworkspace_path\x18\x01 \x01(\t\x12\x19\n\x11workspace_folders\x18\x02 \x03(\t\",\n\x0bVSCodeError\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\">\n\x11InvokeToolRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x02 \x01(\t\";\n\x12InvokeToolResponse\x12\x13\n\x0bresult_json\x18\x01 \x01(\t\x12\x10\n\x08is_error\x18\x02 \x01(\x08\"\x18\n\x16ListVSCodeToolsRequest\"D\n\x17ListVSCodeToolsResponse\x12)\n\x05tools\x18\x01 \x03(\x0b\x32\x1a.abbenay.v1.VSCodeToolInfo\"F\n\x19RegisterToolsNotification\x12)\n\x05tools\x18\x01 \x03(\x0b\x32\x1a.abbenay.v1.VSCodeToolInfo\"W\n\x0eVSCodeToolInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cinput_schema\x18\x03 \x01(\t\x12\x0c\n\x04tags\x18\x04 \x03(\t\"G\n\x17ListVSCodeModelsRequest\x12\x1a\n\rfamily_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_family_filter\"C\n\x18ListVSCodeModelsResponse\x12\'\n\x06models\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.VSCodeModel\"a\n\x0bVSCodeModel\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06vendor\x18\x03 \x01(\t\x12\x0e\n\x06\x66\x61mily\x18\x04 \x01(\t\x12\x18\n\x10max_input_tokens\x18\x05 \x01(\x05\"\xa2\x01\n\x15SendVSCodeChatRequest\x12\x10\n\x08model_id\x18\x01 \x01(\t\x12%\n\x08messages\x18\x02 \x03(\x0b\x32\x13.abbenay.v1.Message\x12\x18\n\x0btemperature\x18\x03 \x01(\x02H\x00\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x04 \x01(\x05H\x01\x88\x01\x01\x42\x0e\n\x0c_temperatureB\r\n\x0b_max_tokens\"E\n\x16SendVSCodeChatResponse\x12+\n\x06\x63hunks\x18\x01 \x03(\x0b\x32\x1b.abbenay.v1.VSCodeChatChunk\"[\n\x0fVSCodeChatChunk\x12\x0e\n\x04text\x18\x01 \x01(\tH\x00\x12/\n\ttool_call\x18\x02 \x01(\x0b\x32\x1a.abbenay.v1.VSCodeToolCallH\x00\x42\x07\n\x05\x63hunk\"G\n\x0eVSCodeToolCall\x12\x0f\n\x07\x63\x61ll_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x03 \x01(\t\"\x15\n\x13ListPoliciesRequest\"@\n\x14ListPoliciesResponse\x12(\n\x08policies\x18\x01 \x03(\x0b\x32\x16.abbenay.v1.PolicyInfo\"U\n\nPolicyInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x62uiltin\x18\x02 \x01(\x08\x12(\n\x06\x63onfig\x18\x03 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfig\"\xc2\x02\n\x0cPolicyConfig\x12\x31\n\x08sampling\x18\x01 \x01(\x0b\x32\x1a.abbenay.v1.PolicySamplingH\x00\x88\x01\x01\x12-\n\x06output\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.PolicyOutputH\x01\x88\x01\x01\x12/\n\x07\x63ontext\x18\x03 \x01(\x0b\x32\x19.abbenay.v1.PolicyContextH\x02\x88\x01\x01\x12)\n\x04tool\x18\x04 \x01(\x0b\x32\x16.abbenay.v1.PolicyToolH\x03\x88\x01\x01\x12\x37\n\x0breliability\x18\x05 \x01(\x0b\x32\x1d.abbenay.v1.PolicyReliabilityH\x04\x88\x01\x01\x42\x0b\n\t_samplingB\t\n\x07_outputB\n\n\x08_contextB\x07\n\x05_toolB\x0e\n\x0c_reliability\"v\n\x0ePolicySampling\x12\x18\n\x0btemperature\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12\x12\n\x05top_p\x18\x02 \x01(\x02H\x01\x88\x01\x01\x12\x12\n\x05top_k\x18\x03 \x01(\x05H\x02\x88\x01\x01\x42\x0e\n\x0c_temperatureB\x08\n\x06_top_pB\x08\n\x06_top_k\"\x8c\x02\n\x0cPolicyOutput\x12\x17\n\nmax_tokens\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12#\n\x16reserved_output_tokens\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x13\n\x06\x66ormat\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\"\n\x15system_prompt_snippet\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x1f\n\x12system_prompt_mode\x18\x05 \x01(\tH\x04\x88\x01\x01\x42\r\n\x0b_max_tokensB\x19\n\x17_reserved_output_tokensB\t\n\x07_formatB\x18\n\x16_system_prompt_snippetB\x15\n\x13_system_prompt_mode\"\x81\x01\n\rPolicyContext\x12\x1e\n\x11\x63ontext_threshold\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12!\n\x14\x63ompression_strategy\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x14\n\x12_context_thresholdB\x17\n\x15_compression_strategy\"l\n\nPolicyTool\x12 \n\x13max_tool_iterations\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\ttool_mode\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x16\n\x14_max_tool_iterationsB\x0c\n\n_tool_mode\"s\n\x11PolicyReliability\x12\"\n\x15retry_on_invalid_json\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x14\n\x07timeout\x18\x02 \x01(\x05H\x01\x88\x01\x01\x42\x18\n\x16_retry_on_invalid_jsonB\n\n\x08_timeout\"\xcd\x01\n\x18RegisterMcpServerRequest\x12\x11\n\tserver_id\x18\x01 \x01(\t\x12+\n\ttransport\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.McpTransport\x12\x17\n\nsession_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0btool_filter\x18\x04 \x03(\t\x12\x1e\n\x11max_response_size\x18\x05 \x01(\x05H\x01\x88\x01\x01\x42\r\n\x0b_session_idB\x14\n\x12_max_response_size\"\xaa\x02\n\x0cMcpTransport\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x14\n\x07\x63ommand\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04\x61rgs\x18\x03 \x03(\t\x12\x10\n\x03url\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x36\n\x07headers\x18\x05 \x03(\x0b\x32%.abbenay.v1.McpTransport.HeadersEntry\x12.\n\x03\x65nv\x18\x06 \x03(\x0b\x32!.abbenay.v1.McpTransport.EnvEntry\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\n\n\x08_commandB\x06\n\x04_url\"U\n\x19RegisterMcpServerResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x18\n\x10\x64iscovered_tools\x18\x03 \x03(\t\"/\n\x1aUnregisterMcpServerRequest\x12\x11\n\tserver_id\x18\x01 \x01(\t\".\n\x1bUnregisterMcpServerResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08*_\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0f\n\x0bROLE_SYSTEM\x10\x01\x12\r\n\tROLE_USER\x10\x02\x12\x12\n\x0eROLE_ASSISTANT\x10\x03\x12\r\n\tROLE_TOOL\x10\x04*\x9b\x01\n\nClientType\x12\x1b\n\x17\x43LIENT_TYPE_UNSPECIFIED\x10\x00\x12\x16\n\x12\x43LIENT_TYPE_VSCODE\x10\x01\x12\x13\n\x0f\x43LIENT_TYPE_CLI\x10\x02\x12\x16\n\x12\x43LIENT_TYPE_PYTHON\x10\x03\x12\x16\n\x12\x43LIENT_TYPE_NODEJS\x10\x04\x12\x13\n\x0f\x43LIENT_TYPE_MCP\x10\x05*\x82\x01\n\x0cReplayFormat\x12\x1d\n\x19REPLAY_FORMAT_UNSPECIFIED\x10\x00\x12\x16\n\x12REPLAY_FORMAT_FULL\x10\x01\x12\x1b\n\x17REPLAY_FORMAT_CONDENSED\x10\x02\x12\x1e\n\x1aREPLAY_FORMAT_SUMMARY_ONLY\x10\x03*`\n\x0bModelSource\x12\x1c\n\x18MODEL_SOURCE_UNSPECIFIED\x10\x00\x12\x17\n\x13MODEL_SOURCE_DIRECT\x10\x01\x12\x1a\n\x16MODEL_SOURCE_VSCODE_LM\x10\x02*u\n\x0cProviderType\x12\x1d\n\x19PROVIDER_TYPE_UNSPECIFIED\x10\x00\x12\x16\n\x12PROVIDER_TYPE_HTTP\x10\x01\x12\x15\n\x11PROVIDER_TYPE_MCP\x10\x02\x12\x17\n\x13PROVIDER_TYPE_LOCAL\x10\x03*\x8c\x01\n\x08LogLevel\x12\x19\n\x15LOG_LEVEL_UNSPECIFIED\x10\x00\x12\x13\n\x0fLOG_LEVEL_ERROR\x10\x01\x12\x12\n\x0eLOG_LEVEL_WARN\x10\x02\x12\x12\n\x0eLOG_LEVEL_INFO\x10\x03\x12\x13\n\x0fLOG_LEVEL_DEBUG\x10\x04\x12\x13\n\x0fLOG_LEVEL_TRACE\x10\x05*u\n\x0bSecretStore\x12\x1c\n\x18SECRET_STORE_UNSPECIFIED\x10\x00\x12\x19\n\x15SECRET_STORE_KEYCHAIN\x10\x01\x12\x14\n\x10SECRET_STORE_ENV\x10\x02\x12\x17\n\x13SECRET_STORE_MEMORY\x10\x03\x32\x9f\x05\n\tMcpBridge\x12Q\n\x0cListMcpTools\x12\x1f.abbenay.v1.ListMcpToolsRequest\x1a .abbenay.v1.ListMcpToolsResponse\x12H\n\x08\x43\x61llTool\x12\x1b.abbenay.v1.CallToolRequest\x1a\x1d.abbenay.v1.ToolResponseChunk0\x01\x12\\\n\x13\x43\x61llToolInteractive\x12\".abbenay.v1.ToolInteractiveRequest\x1a\x1d.abbenay.v1.ToolResponseChunk(\x01\x30\x01\x12T\n\x0e\x43\x61llToolsBatch\x12!.abbenay.v1.CallToolsBatchRequest\x1a\x1d.abbenay.v1.ToolResponseChunk0\x01\x12T\n\rListResources\x12 .abbenay.v1.ListResourcesRequest\x1a!.abbenay.v1.ListResourcesResponse\x12Q\n\x0cReadResource\x12\x1f.abbenay.v1.ReadResourceRequest\x1a .abbenay.v1.ReadResourceResponse\x12N\n\x0bListPrompts\x12\x1e.abbenay.v1.ListPromptsRequest\x1a\x1f.abbenay.v1.ListPromptsResponse\x12H\n\tGetPrompt\x12\x1c.abbenay.v1.GetPromptRequest\x1a\x1d.abbenay.v1.GetPromptResponse2\x82\x1c\n\x07\x41\x62\x62\x65nay\x12\x38\n\x04\x43hat\x12\x17.abbenay.v1.ChatRequest\x1a\x15.abbenay.v1.ChatChunk0\x01\x12\x46\n\x0bSessionChat\x12\x1e.abbenay.v1.SessionChatRequest\x1a\x15.abbenay.v1.ChatChunk0\x01\x12\x46\n\rCreateSession\x12 .abbenay.v1.CreateSessionRequest\x1a\x13.abbenay.v1.Session\x12@\n\nGetSession\x12\x1d.abbenay.v1.GetSessionRequest\x1a\x13.abbenay.v1.Session\x12Q\n\x0cListSessions\x12\x1f.abbenay.v1.ListSessionsRequest\x1a .abbenay.v1.ListSessionsResponse\x12\x44\n\rDeleteSession\x12 .abbenay.v1.DeleteSessionRequest\x1a\x11.abbenay.v1.Empty\x12M\n\rWatchSessions\x12 .abbenay.v1.WatchSessionsRequest\x1a\x18.abbenay.v1.SessionEvent0\x01\x12T\n\rReplaySession\x12 .abbenay.v1.ReplaySessionRequest\x1a!.abbenay.v1.ReplaySessionResponse\x12]\n\x10SummarizeSession\x12#.abbenay.v1.SummarizeSessionRequest\x1a$.abbenay.v1.SummarizeSessionResponse\x12\x42\n\x0b\x46orkSession\x12\x1e.abbenay.v1.ForkSessionRequest\x1a\x13.abbenay.v1.Session\x12T\n\rExportSession\x12 .abbenay.v1.ExportSessionRequest\x1a!.abbenay.v1.ExportSessionResponse\x12\x46\n\rImportSession\x12 .abbenay.v1.ImportSessionRequest\x1a\x13.abbenay.v1.Session\x12K\n\nListModels\x12\x1d.abbenay.v1.ListModelsRequest\x1a\x1e.abbenay.v1.ListModelsResponse\x12W\n\x0e\x44iscoverModels\x12!.abbenay.v1.DiscoverModelsRequest\x1a\".abbenay.v1.DiscoverModelsResponse\x12T\n\rListProviders\x12 .abbenay.v1.ListProvidersRequest\x1a!.abbenay.v1.ListProvidersResponse\x12U\n\x11GetProviderStatus\x12$.abbenay.v1.GetProviderStatusRequest\x1a\x1a.abbenay.v1.ProviderStatus\x12N\n\x0bListEngines\x12\x1e.abbenay.v1.ListEnginesRequest\x1a\x1f.abbenay.v1.ListEnginesResponse\x12`\n\x11\x43onfigureProvider\x12$.abbenay.v1.ConfigureProviderRequest\x1a%.abbenay.v1.ConfigureProviderResponse\x12\x46\n\x0eRemoveProvider\x12!.abbenay.v1.RemoveProviderRequest\x1a\x11.abbenay.v1.Empty\x12i\n\x14GetProviderTemplates\x12\'.abbenay.v1.GetProviderTemplatesRequest\x1a(.abbenay.v1.GetProviderTemplatesResponse\x12Q\n\x0cListPolicies\x12\x1f.abbenay.v1.ListPoliciesRequest\x1a .abbenay.v1.ListPoliciesResponse\x12\x42\n\x0c\x43reatePolicy\x12\x1f.abbenay.v1.CreatePolicyRequest\x1a\x11.abbenay.v1.Empty\x12\x42\n\x0c\x44\x65letePolicy\x12\x1f.abbenay.v1.DeletePolicyRequest\x1a\x11.abbenay.v1.Empty\x12H\n\tListTools\x12\x1c.abbenay.v1.ListToolsRequest\x1a\x1d.abbenay.v1.ListToolsResponse\x12N\n\x0b\x45xecuteTool\x12\x1e.abbenay.v1.ExecuteToolRequest\x1a\x1f.abbenay.v1.ExecuteToolResponse\x12H\n\tGetConfig\x12\x1c.abbenay.v1.GetConfigRequest\x1a\x1d.abbenay.v1.GetConfigResponse\x12N\n\x0cUpdateConfig\x12\x1f.abbenay.v1.UpdateConfigRequest\x1a\x1d.abbenay.v1.GetConfigResponse\x12Q\n\x0cGetKeyStatus\x12\x1f.abbenay.v1.GetKeyStatusRequest\x1a .abbenay.v1.GetKeyStatusResponse\x12i\n\x14ListMcpServerConfigs\x12\'.abbenay.v1.ListMcpServerConfigsRequest\x1a(.abbenay.v1.ListMcpServerConfigsResponse\x12N\n\x12ReconnectMcpServer\x12%.abbenay.v1.ReconnectMcpServerRequest\x1a\x11.abbenay.v1.Empty\x12H\n\tGetSecret\x12\x1c.abbenay.v1.GetSecretRequest\x1a\x1d.abbenay.v1.GetSecretResponse\x12<\n\tSetSecret\x12\x1c.abbenay.v1.SetSecretRequest\x1a\x11.abbenay.v1.Empty\x12\x42\n\x0c\x44\x65leteSecret\x12\x1f.abbenay.v1.DeleteSecretRequest\x1a\x11.abbenay.v1.Empty\x12N\n\x0bListSecrets\x12\x1e.abbenay.v1.ListSecretsRequest\x1a\x1f.abbenay.v1.ListSecretsResponse\x12\x45\n\x08Register\x12\x1b.abbenay.v1.RegisterRequest\x1a\x1c.abbenay.v1.RegisterResponse\x12>\n\nUnregister\x12\x1d.abbenay.v1.UnregisterRequest\x1a\x11.abbenay.v1.Empty\x12\x43\n\tGetStatus\x12\x1c.abbenay.v1.GetStatusRequest\x1a\x18.abbenay.v1.DaemonStatus\x12o\n\x16GetConnectedWorkspaces\x12).abbenay.v1.GetConnectedWorkspacesRequest\x1a*.abbenay.v1.GetConnectedWorkspacesResponse\x12:\n\x08Shutdown\x12\x1b.abbenay.v1.ShutdownRequest\x1a\x11.abbenay.v1.Empty\x12N\n\x0bHealthCheck\x12\x1e.abbenay.v1.HealthCheckRequest\x1a\x1f.abbenay.v1.HealthCheckResponse\x12W\n\x0eStartWebServer\x12!.abbenay.v1.StartWebServerRequest\x1a\".abbenay.v1.StartWebServerResponse\x12\x44\n\rStopWebServer\x12 .abbenay.v1.StopWebServerRequest\x1a\x11.abbenay.v1.Empty\x12`\n\x11RegisterMcpServer\x12$.abbenay.v1.RegisterMcpServerRequest\x1a%.abbenay.v1.RegisterMcpServerResponse\x12\x66\n\x13UnregisterMcpServer\x12&.abbenay.v1.UnregisterMcpServerRequest\x1a\'.abbenay.v1.UnregisterMcpServerResponse\x12I\n\x0cVSCodeStream\x12\x1a.abbenay.v1.VSCodeResponse\x1a\x19.abbenay.v1.VSCodeRequest(\x01\x30\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x61\x62\x62\x65nay/v1/service.proto\x12\nabbenay.v1\"\x07\n\x05\x45mpty\"+\n\tTimestamp\x12\x0f\n\x07seconds\x18\x01 \x01(\x03\x12\r\n\x05nanos\x18\x02 \x01(\x05\"C\n\x13ListMcpToolsRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_server_filter\":\n\x14ListMcpToolsResponse\x12\"\n\x05tools\x18\x01 \x03(\x0b\x32\x13.abbenay.v1.McpTool\"o\n\x07McpTool\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x19\n\x11input_schema_json\x18\x03 \x01(\t\x12\x11\n\tserver_id\x18\x04 \x01(\t\x12\x13\n\x0b\x61nnotations\x18\x05 \x03(\t\"\xcf\x01\n\x0f\x43\x61llToolRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x02 \x01(\t\x12\x17\n\nrequest_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x39\n\x07\x63ontext\x18\x04 \x03(\x0b\x32(.abbenay.v1.CallToolRequest.ContextEntry\x1a.\n\x0c\x43ontextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b_request_id\"\xc9\x02\n\x11ToolResponseChunk\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12,\n\x08progress\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.ToolProgressH\x00\x12/\n\ntext_delta\x18\x03 \x01(\x0b\x32\x19.abbenay.v1.ToolTextDeltaH\x00\x12\x30\n\x07partial\x18\x04 \x01(\x0b\x32\x1d.abbenay.v1.ToolPartialResultH\x00\x12\x33\n\x0c\x66inal_result\x18\x05 \x01(\x0b\x32\x1b.abbenay.v1.ToolFinalResultH\x00\x12&\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x15.abbenay.v1.ToolErrorH\x00\x12(\n\x06prompt\x18\x07 \x01(\x0b\x32\x16.abbenay.v1.ToolPromptH\x00\x42\x08\n\x06result\"]\n\x0cToolProgress\x12\x12\n\npercentage\x18\x01 \x01(\x02\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x18\n\x0b\x65ta_seconds\x18\x03 \x01(\x03H\x00\x88\x01\x01\x42\x0e\n\x0c_eta_seconds\"\x1d\n\rToolTextDelta\x12\x0c\n\x04text\x18\x01 \x01(\t\";\n\x11ToolPartialResult\x12\x14\n\x0c\x63ontent_json\x18\x01 \x01(\t\x12\x10\n\x08is_final\x18\x02 \x01(\x08\"M\n\x0fToolFinalResult\x12(\n\x07\x63ontent\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.ToolContent\x12\x10\n\x08is_error\x18\x02 \x01(\x08\"W\n\x0bToolContent\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x11\n\tmime_type\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0b\n\x03uri\x18\x05 \x01(\t\"V\n\tToolError\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x19\n\x0c\x64\x65tails_json\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0f\n\r_details_json\"o\n\nToolPrompt\x12\x11\n\tprompt_id\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x0f\n\x07options\x18\x03 \x03(\t\x12\x1a\n\rdefault_value\x18\x04 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_default_value\"\xae\x01\n\x16ToolInteractiveRequest\x12+\n\x04\x63\x61ll\x18\x01 \x01(\x0b\x32\x1b.abbenay.v1.CallToolRequestH\x00\x12\x32\n\x08response\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.ToolPromptResponseH\x00\x12(\n\x06\x63\x61ncel\x18\x03 \x01(\x0b\x32\x16.abbenay.v1.ToolCancelH\x00\x42\t\n\x07request\"9\n\x12ToolPromptResponse\x12\x11\n\tprompt_id\x18\x01 \x01(\t\x12\x10\n\x08response\x18\x02 \x01(\t\"0\n\nToolCancel\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\"Z\n\x15\x43\x61llToolsBatchRequest\x12*\n\x05\x63\x61lls\x18\x01 \x03(\x0b\x32\x1b.abbenay.v1.CallToolRequest\x12\x15\n\rstop_on_error\x18\x02 \x01(\x08\"l\n\x14ListResourcesRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nuri_prefix\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x10\n\x0e_server_filterB\r\n\x0b_uri_prefix\"C\n\x15ListResourcesResponse\x12*\n\tresources\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.McpResource\"P\n\x0bMcpResource\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x11\n\tmime_type\x18\x04 \x01(\t\"\"\n\x13ReadResourceRequest\x12\x0b\n\x03uri\x18\x01 \x01(\t\"A\n\x14ReadResourceResponse\x12)\n\x08\x63ontents\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.ToolContent\"B\n\x12ListPromptsRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_server_filter\"=\n\x13ListPromptsResponse\x12&\n\x07prompts\x18\x01 \x03(\x0b\x32\x15.abbenay.v1.McpPrompt\"`\n\tMcpPrompt\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x30\n\targuments\x18\x03 \x03(\x0b\x32\x1d.abbenay.v1.McpPromptArgument\"H\n\x11McpPromptArgument\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x10\n\x08required\x18\x03 \x01(\x08\"\x92\x01\n\x10GetPromptRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12>\n\targuments\x18\x02 \x03(\x0b\x32+.abbenay.v1.GetPromptRequest.ArgumentsEntry\x1a\x30\n\x0e\x41rgumentsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"U\n\x11GetPromptResponse\x12\x13\n\x0b\x64\x65scription\x18\x01 \x01(\t\x12+\n\x08messages\x18\x02 \x03(\x0b\x32\x19.abbenay.v1.PromptMessage\".\n\rPromptMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\"\xc8\x01\n\x0b\x43hatRequest\x12\r\n\x05model\x18\x01 \x01(\t\x12%\n\x08messages\x18\x02 \x03(\x0b\x32\x13.abbenay.v1.Message\x12(\n\x07options\x18\x03 \x01(\x0b\x32\x17.abbenay.v1.ChatOptions\x12\x1f\n\x05tools\x18\x04 \x03(\x0b\x32\x10.abbenay.v1.Tool\x12-\n\x06policy\x18\x05 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfigH\x00\x88\x01\x01\x42\t\n\x07_policy\"\xb2\x01\n\x12SessionChatRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12$\n\x07message\x18\x02 \x01(\x0b\x32\x13.abbenay.v1.Message\x12(\n\x07options\x18\x03 \x01(\x0b\x32\x17.abbenay.v1.ChatOptions\x12-\n\x06policy\x18\x04 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfigH\x00\x88\x01\x01\x42\t\n\x07_policy\"\xb9\x02\n\x0b\x43hatOptions\x12\x18\n\x0btemperature\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x12\n\x05top_p\x18\x03 \x01(\x02H\x02\x88\x01\x01\x12\x0c\n\x04stop\x18\x04 \x03(\t\x12\x14\n\x0c\x65nable_tools\x18\x05 \x01(\x08\x12\x1b\n\x13max_tool_iterations\x18\x06 \x01(\x05\x12\x13\n\x0btool_filter\x18\x07 \x03(\t\x12\x12\n\x05top_k\x18\x08 \x01(\x05H\x03\x88\x01\x01\x12\x14\n\x07timeout\x18\t \x01(\x05H\x04\x88\x01\x01\x12\x16\n\ttool_mode\x18\n \x01(\tH\x05\x88\x01\x01\x42\x0e\n\x0c_temperatureB\r\n\x0b_max_tokensB\x08\n\x06_top_pB\x08\n\x06_top_kB\n\n\x08_timeoutB\x0c\n\n_tool_mode\"\xc3\x02\n\tChatChunk\x12%\n\x04text\x18\x01 \x01(\x0b\x32\x15.abbenay.v1.TextChunkH\x00\x12.\n\ttool_call\x18\x02 \x01(\x0b\x32\x19.abbenay.v1.ToolCallChunkH\x00\x12\x32\n\x0btool_result\x18\x03 \x01(\x0b\x32\x1b.abbenay.v1.ToolResultChunkH\x00\x12)\n\x06prompt\x18\x04 \x01(\x0b\x32\x17.abbenay.v1.PromptChunkH\x00\x12\'\n\x05usage\x18\x05 \x01(\x0b\x32\x16.abbenay.v1.UsageChunkH\x00\x12\'\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x16.abbenay.v1.ErrorChunkH\x00\x12%\n\x04\x64one\x18\x07 \x01(\x0b\x32\x15.abbenay.v1.DoneChunkH\x00\x42\x07\n\x05\x63hunk\"\x19\n\tTextChunk\x12\x0c\n\x04text\x18\x01 \x01(\t\"<\n\rToolCallChunk\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\targuments\x18\x03 \x01(\t\"X\n\x0fToolResultChunk\x12\x14\n\x0ctool_call_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\t\x12\x10\n\x08is_error\x18\x04 \x01(\x08\"\\\n\x0bPromptChunk\x12\x14\n\x0ctool_call_id\x18\x01 \x01(\t\x12\x11\n\ttool_name\x18\x02 \x01(\t\x12\x13\n\x0bprompt_text\x18\x03 \x01(\t\x12\x0f\n\x07options\x18\x04 \x03(\t\"T\n\nUsageChunk\x12\x15\n\rprompt_tokens\x18\x01 \x01(\x05\x12\x19\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x12\x14\n\x0ctotal_tokens\x18\x03 \x01(\x05\"+\n\nErrorChunk\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\"\"\n\tDoneChunk\x12\x15\n\rfinish_reason\x18\x01 \x01(\t\"\x88\x01\n\x07Message\x12\x1e\n\x04role\x18\x01 \x01(\x0e\x32\x10.abbenay.v1.Role\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12(\n\ntool_calls\x18\x03 \x03(\x0b\x32\x14.abbenay.v1.ToolCall\x12\x14\n\x0ctool_call_id\x18\x04 \x01(\t\x12\x0c\n\x04name\x18\x05 \x01(\t\"7\n\x08ToolCall\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x11\n\targuments\x18\x03 \x01(\t\"\xc4\x03\n\x07Session\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\x12\r\n\x05topic\x18\x03 \x01(\t\x12%\n\x08messages\x18\x04 \x03(\x0b\x32\x13.abbenay.v1.Message\x12*\n\ncreated_by\x18\x05 \x01(\x0b\x32\x16.abbenay.v1.ClientInfo\x12)\n\ncreated_at\x18\x06 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12)\n\nupdated_at\x18\x07 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12\x33\n\x08metadata\x18\x08 \x03(\x0b\x32!.abbenay.v1.Session.MetadataEntry\x12\x18\n\x0b\x66orked_from\x18\t \x01(\tH\x00\x88\x01\x01\x12\x17\n\nfork_point\x18\n \x01(\x05H\x01\x88\x01\x01\x12\x1b\n\x0e\x63\x61\x63hed_summary\x18\x0b \x01(\tH\x02\x88\x01\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0e\n\x0c_forked_fromB\r\n\x0b_fork_pointB\x11\n\x0f_cached_summary\"Z\n\nClientInfo\x12+\n\x0b\x63lient_type\x18\x01 \x01(\x0e\x32\x16.abbenay.v1.ClientType\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x0c\n\x04user\x18\x03 \x01(\t\"\xb6\x01\n\x14\x43reateSessionRequest\x12\r\n\x05model\x18\x01 \x01(\t\x12\x12\n\x05topic\x18\x02 \x01(\tH\x00\x88\x01\x01\x12@\n\x08metadata\x18\x03 \x03(\x0b\x32..abbenay.v1.CreateSessionRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x08\n\x06_topic\"A\n\x11GetSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x18\n\x10include_messages\x18\x02 \x01(\x08\"\x8e\x01\n\x13ListSessionsRequest\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x1a\n\rsource_filter\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x19\n\x0cmodel_filter\x18\x04 \x01(\tH\x01\x88\x01\x01\x42\x10\n\x0e_source_filterB\x0f\n\r_model_filter\"Y\n\x14ListSessionsResponse\x12,\n\x08sessions\x18\x01 \x03(\x0b\x32\x1a.abbenay.v1.SessionSummary\x12\x13\n\x0btotal_count\x18\x02 \x01(\x05\"\xcf\x01\n\x0eSessionSummary\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\x12\r\n\x05topic\x18\x03 \x01(\t\x12\x15\n\rmessage_count\x18\x04 \x01(\x05\x12&\n\x06source\x18\x05 \x01(\x0e\x32\x16.abbenay.v1.ClientType\x12)\n\ncreated_at\x18\x06 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12)\n\nupdated_at\x18\x07 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\"*\n\x14\x44\x65leteSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\"\x16\n\x14WatchSessionsRequest\"\xb8\x01\n\x0cSessionEvent\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12-\n\x07\x63reated\x18\x02 \x01(\x0b\x32\x1a.abbenay.v1.SessionCreatedH\x00\x12-\n\x07updated\x18\x03 \x01(\x0b\x32\x1a.abbenay.v1.SessionUpdatedH\x00\x12-\n\x07\x64\x65leted\x18\x04 \x01(\x0b\x32\x1a.abbenay.v1.SessionDeletedH\x00\x42\x07\n\x05\x65vent\"=\n\x0eSessionCreated\x12+\n\x07session\x18\x01 \x01(\x0b\x32\x1a.abbenay.v1.SessionSummary\"X\n\x0eSessionUpdated\x12+\n\x07session\x18\x01 \x01(\x0b\x32\x1a.abbenay.v1.SessionSummary\x12\x19\n\x11new_message_count\x18\x02 \x01(\x05\"\x10\n\x0eSessionDeleted\"\x80\x01\n\x14ReplaySessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12(\n\x06\x66ormat\x18\x02 \x01(\x0e\x32\x18.abbenay.v1.ReplayFormat\x12\x19\n\x0cmax_messages\x18\x03 \x01(\x05H\x00\x88\x01\x01\x42\x0f\n\r_max_messages\"a\n\x15ReplaySessionResponse\x12\x19\n\x11\x66ormatted_content\x18\x01 \x01(\t\x12\x15\n\rmessage_count\x18\x02 \x01(\x05\x12\x16\n\x0etoken_estimate\x18\x03 \x01(\x05\"_\n\x17SummarizeSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1c\n\x0fsummarize_model\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x12\n\x10_summarize_model\"?\n\x18SummarizeSessionResponse\x12\x0f\n\x07summary\x18\x01 \x01(\t\x12\x12\n\nfrom_cache\x18\x02 \x01(\x08\"v\n\x12\x46orkSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x16\n\tnew_model\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nfork_point\x18\x03 \x01(\x05H\x01\x88\x01\x01\x42\x0c\n\n_new_modelB\r\n\x0b_fork_point\"b\n\x14\x45xportSessionRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x1c\n\x14include_tool_results\x18\x02 \x01(\x08\x12\x18\n\x10include_metadata\x18\x03 \x01(\x08\"-\n\x15\x45xportSessionResponse\x12\x14\n\x0cjson_content\x18\x01 \x01(\t\"E\n\x14ImportSessionRequest\x12\x14\n\x0cjson_content\x18\x01 \x01(\t\x12\x17\n\x0fgenerate_new_id\x18\x02 \x01(\x08\"^\n\x11ListModelsRequest\x12\x1c\n\x0fprovider_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\x0fworkspace_paths\x18\x02 \x03(\tB\x12\n\x10_provider_filter\"7\n\x12ListModelsResponse\x12!\n\x06models\x18\x01 \x03(\x0b\x32\x11.abbenay.v1.Model\"\x9a\x01\n\x15\x44iscoverModelsRequest\x12\x11\n\tengine_id\x18\x01 \x01(\t\x12\x14\n\x07\x61pi_key\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08\x62\x61se_url\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bprovider_id\x18\x04 \x01(\tH\x02\x88\x01\x01\x42\n\n\x08_api_keyB\x0b\n\t_base_urlB\x0e\n\x0c_provider_id\";\n\x16\x44iscoverModelsResponse\x12!\n\x06models\x18\x01 \x03(\x0b\x32\x11.abbenay.v1.Model\"\x93\x02\n\x05Model\x12\n\n\x02id\x18\x01 \x01(\t\x12\x10\n\x08provider\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x33\n\x0c\x63\x61pabilities\x18\x04 \x01(\x0b\x32\x1d.abbenay.v1.ModelCapabilities\x12\'\n\x06source\x18\x05 \x01(\x0e\x32\x17.abbenay.v1.ModelSource\x12\x0e\n\x06\x65ngine\x18\x06 \x01(\t\x12,\n\x06params\x18\x07 \x01(\x0b\x32\x17.abbenay.v1.ModelParamsH\x00\x88\x01\x01\x12\x17\n\x0f\x65ngine_model_id\x18\x08 \x01(\t\x12\x13\n\x06policy\x18\t \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_paramsB\t\n\x07_policy\"\xa3\x02\n\x0bModelParams\x12\x18\n\x0btemperature\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12\x12\n\x05top_p\x18\x02 \x01(\x02H\x01\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x03 \x01(\x05H\x02\x88\x01\x01\x12\x1a\n\rsystem_prompt\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x1f\n\x12system_prompt_mode\x18\x05 \x01(\tH\x04\x88\x01\x01\x12\x12\n\x05top_k\x18\x06 \x01(\x05H\x05\x88\x01\x01\x12\x14\n\x07timeout\x18\x07 \x01(\x05H\x06\x88\x01\x01\x42\x0e\n\x0c_temperatureB\x08\n\x06_top_pB\r\n\x0b_max_tokensB\x10\n\x0e_system_promptB\x15\n\x13_system_prompt_modeB\x08\n\x06_top_kB\n\n\x08_timeout\"\x90\x01\n\x11ModelCapabilities\x12\x1a\n\x12supports_streaming\x18\x01 \x01(\x08\x12\x16\n\x0esupports_tools\x18\x02 \x01(\x08\x12\x17\n\x0fsupports_vision\x18\x03 \x01(\x08\x12\x1b\n\x0e\x63ontext_window\x18\x04 \x01(\x05H\x00\x88\x01\x01\x42\x11\n\x0f_context_window\"/\n\x14ListProvidersRequest\x12\x17\n\x0fworkspace_paths\x18\x01 \x03(\t\"@\n\x15ListProvidersResponse\x12\'\n\tproviders\x18\x01 \x03(\x0b\x32\x14.abbenay.v1.Provider\"\xea\x01\n\x08Provider\x12\n\n\x02id\x18\x01 \x01(\t\x12\x12\n\nconfigured\x18\x02 \x01(\x08\x12\x0f\n\x07healthy\x18\x03 \x01(\x08\x12/\n\rprovider_type\x18\x04 \x01(\x0e\x32\x18.abbenay.v1.ProviderType\x12\x14\n\x0crequires_key\x18\x05 \x01(\x08\x12\x1d\n\x10\x64\x65\x66\x61ult_base_url\x18\x06 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06\x65ngine\x18\x07 \x01(\t\x12\x15\n\x08\x62\x61se_url\x18\x08 \x01(\tH\x01\x88\x01\x01\x42\x13\n\x11_default_base_urlB\x0b\n\t_base_url\"/\n\x18GetProviderStatusRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\"\x93\x01\n\x0eProviderStatus\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x12\n\nconfigured\x18\x02 \x01(\x08\x12\x0f\n\x07healthy\x18\x03 \x01(\x08\x12\x12\n\x05\x65rror\x18\x04 \x01(\tH\x00\x88\x01\x01\x12)\n\nlast_check\x18\x05 \x01(\x0b\x32\x15.abbenay.v1.TimestampB\x08\n\x06_error\"\x14\n\x12ListEnginesRequest\":\n\x13ListEnginesResponse\x12#\n\x07\x65ngines\x18\x01 \x03(\x0b\x32\x12.abbenay.v1.Engine\"\xbc\x01\n\x06\x45ngine\x12\n\n\x02id\x18\x01 \x01(\t\x12\x14\n\x0crequires_key\x18\x02 \x01(\x08\x12\x1d\n\x10\x64\x65\x66\x61ult_base_url\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x1c\n\x0f\x64\x65\x66\x61ult_env_var\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x19\n\x0c\x64isplay_name\x18\x05 \x01(\tH\x02\x88\x01\x01\x42\x13\n\x11_default_base_urlB\x12\n\x10_default_env_varB\x0f\n\r_display_name\"@\n\x10ListToolsRequest\x12\x1a\n\rserver_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_server_filter\"4\n\x11ListToolsResponse\x12\x1f\n\x05tools\x18\x01 \x03(\x0b\x32\x10.abbenay.v1.Tool\"O\n\x04Tool\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cinput_schema\x18\x03 \x01(\t\x12\x0e\n\x06server\x18\x04 \x01(\t\"5\n\x12\x45xecuteToolRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x11\n\targuments\x18\x02 \x01(\t\"8\n\x13\x45xecuteToolResponse\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\t\x12\x10\n\x08is_error\x18\x02 \x01(\x08\"6\n\x10GetConfigRequest\x12\x15\n\x08location\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_location\"E\n\x11GetConfigResponse\x12\"\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x12.abbenay.v1.Config\x12\x0c\n\x04path\x18\x02 \x01(\t\"\xd9\x03\n\x06\x43onfig\x12\x34\n\tproviders\x18\x01 \x03(\x0b\x32!.abbenay.v1.Config.ProvidersEntry\x12\x37\n\x0bmcp_servers\x18\x02 \x03(\x0b\x32\".abbenay.v1.Config.McpServersEntry\x12\x34\n\x0btool_policy\x18\x03 \x01(\x0b\x32\x1f.abbenay.v1.ToolPolicyConfigMsg\x12\x34\n\tconsumers\x18\x04 \x03(\x0b\x32!.abbenay.v1.Config.ConsumersEntry\x1aP\n\x0eProvidersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12-\n\x05value\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.FullProviderConfig:\x02\x38\x01\x1aQ\n\x0fMcpServersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12-\n\x05value\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.McpServerConfigMsg:\x02\x38\x01\x1aO\n\x0e\x43onsumersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12,\n\x05value\x18\x02 \x01(\x0b\x32\x1d.abbenay.v1.ConsumerConfigMsg:\x02\x38\x01\"\xba\x03\n\x12\x46ullProviderConfig\x12\x0e\n\x06\x65ngine\x18\x01 \x01(\t\x12\"\n\x15\x61pi_key_keychain_name\x18\x02 \x01(\tH\x00\x88\x01\x01\x12!\n\x14\x61pi_key_env_var_name\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x15\n\x08\x62\x61se_url\x18\x04 \x01(\tH\x02\x88\x01\x01\x12:\n\x06models\x18\x05 \x03(\x0b\x32*.abbenay.v1.FullProviderConfig.ModelsEntry\x12\x18\n\x0bsecret_name\x18\x06 \x01(\tH\x03\x88\x01\x01\x12\x32\n\x0csecret_store\x18\x07 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x04\x88\x01\x01\x1aK\n\x0bModelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05value\x18\x02 \x01(\x0b\x32\x1c.abbenay.v1.ModelParamConfig:\x02\x38\x01\x42\x18\n\x16_api_key_keychain_nameB\x17\n\x15_api_key_env_var_nameB\x0b\n\t_base_urlB\x0e\n\x0c_secret_nameB\x0f\n\r_secret_store\"\xec\x02\n\x10ModelParamConfig\x12\x15\n\x08model_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x06policy\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x1a\n\rsystem_prompt\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x1f\n\x12system_prompt_mode\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x18\n\x0btemperature\x18\x05 \x01(\x02H\x04\x88\x01\x01\x12\x12\n\x05top_p\x18\x06 \x01(\x02H\x05\x88\x01\x01\x12\x12\n\x05top_k\x18\x07 \x01(\x05H\x06\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x08 \x01(\x05H\x07\x88\x01\x01\x12\x14\n\x07timeout\x18\t \x01(\x05H\x08\x88\x01\x01\x42\x0b\n\t_model_idB\t\n\x07_policyB\x10\n\x0e_system_promptB\x15\n\x13_system_prompt_modeB\x0e\n\x0c_temperatureB\x08\n\x06_top_pB\x08\n\x06_top_kB\r\n\x0b_max_tokensB\n\n\x08_timeout\"\x88\x03\n\x12McpServerConfigMsg\x12\x14\n\x07\x63ommand\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x03(\t\x12\x10\n\x03url\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x11\n\ttransport\x18\x04 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x05 \x01(\x08\x12<\n\x07headers\x18\x06 \x03(\x0b\x32+.abbenay.v1.McpServerConfigMsg.HeadersEntry\x12\x34\n\x03\x65nv\x18\x07 \x03(\x0b\x32\'.abbenay.v1.McpServerConfigMsg.EnvEntry\x12\x1e\n\x11max_response_size\x18\x08 \x01(\x05H\x02\x88\x01\x01\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\n\n\x08_commandB\x06\n\x04_urlB\x14\n\x12_max_response_size\"\x86\x02\n\x13ToolPolicyConfigMsg\x12 \n\x13max_tool_iterations\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x61uto_approve\x18\x02 \x03(\t\x12\x18\n\x10require_approval\x18\x03 \x03(\t\x12\x16\n\x0e\x64isabled_tools\x18\x04 \x03(\t\x12=\n\x07\x61liases\x18\x05 \x03(\x0b\x32,.abbenay.v1.ToolPolicyConfigMsg.AliasesEntry\x1a.\n\x0c\x41liasesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x16\n\x14_max_tool_iterations\"\xa4\x01\n\x11\x43onsumerConfigMsg\x12\x16\n\ttoken_env\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x1b\n\x0etoken_keychain\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x39\n\x0c\x63\x61pabilities\x18\x03 \x01(\x0b\x32#.abbenay.v1.ConsumerCapabilitiesMsgB\x0c\n\n_token_envB\x11\n\x0f_token_keychain\"\x9b\x02\n\x17\x43onsumerCapabilitiesMsg\x12\x1a\n\rinline_policy\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x19\n\x0cmcp_register\x18\x02 \x01(\x08H\x01\x88\x01\x01\x12\x14\n\x07secrets\x18\x03 \x01(\x08H\x02\x88\x01\x01\x12\x13\n\x06\x63onfig\x18\x04 \x01(\x08H\x03\x88\x01\x01\x12\x16\n\tproviders\x18\x05 \x01(\x08H\x04\x88\x01\x01\x12\x15\n\x08shutdown\x18\x06 \x01(\x08H\x05\x88\x01\x01\x12\x11\n\x04\x63hat\x18\x07 \x01(\x08H\x06\x88\x01\x01\x42\x10\n\x0e_inline_policyB\x0f\n\r_mcp_registerB\n\n\x08_secretsB\t\n\x07_configB\x0c\n\n_providersB\x0b\n\t_shutdownB\x07\n\x05_chat\"]\n\x13UpdateConfigRequest\x12\"\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x12.abbenay.v1.Config\x12\x15\n\x08location\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0b\n\t_location\"\x80\x03\n\x18\x43onfigureProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x13\n\x06\x65ngine\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07\x61pi_key\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x19\n\x0c\x65nv_var_name\x18\x04 \x01(\tH\x02\x88\x01\x01\x12\x15\n\x08\x62\x61se_url\x18\x05 \x01(\tH\x03\x88\x01\x01\x12\x13\n\x06target\x18\x06 \x01(\tH\x04\x88\x01\x01\x12\x1b\n\x0eworkspace_path\x18\x07 \x01(\tH\x05\x88\x01\x01\x12\x18\n\x0bsecret_name\x18\x08 \x01(\tH\x06\x88\x01\x01\x12\x32\n\x0csecret_store\x18\t \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x07\x88\x01\x01\x42\t\n\x07_engineB\n\n\x08_api_keyB\x0f\n\r_env_var_nameB\x0b\n\t_base_urlB\t\n\x07_targetB\x11\n\x0f_workspace_pathB\x0e\n\x0c_secret_nameB\x0f\n\r_secret_store\",\n\x19\x43onfigureProviderResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"|\n\x15RemoveProviderRequest\x12\x13\n\x0bprovider_id\x18\x01 \x01(\t\x12\x13\n\x06target\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x1b\n\x0eworkspace_path\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07_targetB\x11\n\x0f_workspace_path\"\x1d\n\x1bGetProviderTemplatesRequest\"O\n\x1cGetProviderTemplatesResponse\x12/\n\ttemplates\x18\x01 \x03(\x0b\x32\x1c.abbenay.v1.ProviderTemplate\"\x84\x01\n\x10ProviderTemplate\x12\x0e\n\x06\x65ngine\x18\x01 \x01(\t\x12\x16\n\x0esuggested_name\x18\x02 \x01(\t\x12\x1d\n\x10\x64\x65\x66\x61ult_base_url\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x0crequires_key\x18\x04 \x01(\x08\x42\x13\n\x11_default_base_url\"3\n\x13GetKeyStatusRequest\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"&\n\x14GetKeyStatusResponse\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\"\x1d\n\x1bListMcpServerConfigsRequest\"U\n\x1cListMcpServerConfigsResponse\x12\x35\n\x0bmcp_servers\x18\x01 \x03(\x0b\x32 .abbenay.v1.McpServerStatusEntry\"\x88\x01\n\x14McpServerStatusEntry\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x12\x0e\n\x06status\x18\x04 \x01(\t\x12\x12\n\ntool_count\x18\x05 \x01(\x05\x12\x12\n\x05\x65rror\x18\x06 \x01(\tH\x00\x88\x01\x01\x42\x08\n\x06_error\".\n\x19ReconnectMcpServerRequest\x12\x11\n\tserver_id\x18\x01 \x01(\t\"M\n\x13\x43reatePolicyRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12(\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfig\"#\n\x13\x44\x65letePolicyRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"V\n\x10GetSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05store\x18\x02 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x00\x88\x01\x01\x42\x08\n\x06_store\"\"\n\x11GetSecretResponse\x12\r\n\x05value\x18\x01 \x01(\t\"e\n\x10SetSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x12+\n\x05store\x18\x03 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x00\x88\x01\x01\x42\x08\n\x06_store\"Y\n\x13\x44\x65leteSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12+\n\x05store\x18\x02 \x01(\x0e\x32\x17.abbenay.v1.SecretStoreH\x00\x88\x01\x01\x42\x08\n\x06_store\"\x14\n\x12ListSecretsRequest\">\n\x13ListSecretsResponse\x12\'\n\x07secrets\x18\x01 \x03(\x0b\x32\x16.abbenay.v1.SecretInfo\"T\n\nSecretInfo\x12\x0b\n\x03key\x18\x01 \x01(\t\x12&\n\x05store\x18\x02 \x01(\x0e\x32\x17.abbenay.v1.SecretStore\x12\x11\n\thas_value\x18\x03 \x01(\x08\"~\n\x0fRegisterRequest\x12&\n\x06\x63lient\x18\x01 \x01(\x0b\x32\x16.abbenay.v1.ClientInfo\x12\x12\n\nis_spawner\x18\x02 \x01(\x08\x12\x16\n\x0eworkspace_path\x18\x03 \x01(\t\x12\x17\n\x0fworkspace_paths\x18\x04 \x03(\t\"@\n\x10RegisterResponse\x12\x11\n\tclient_id\x18\x01 \x01(\t\x12\x19\n\x11\x63onnected_clients\x18\x02 \x01(\x05\"&\n\x11UnregisterRequest\x12\x11\n\tclient_id\x18\x01 \x01(\t\"\x12\n\x10GetStatusRequest\"\x1f\n\x1dGetConnectedWorkspacesRequest\"4\n\x1eGetConnectedWorkspacesResponse\x12\x12\n\nworkspaces\x18\x01 \x03(\t\"\xcc\x01\n\x0c\x44\x61\x65monStatus\x12\x0f\n\x07version\x18\x01 \x01(\t\x12)\n\nstarted_at\x18\x02 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12\x19\n\x11\x63onnected_clients\x18\x03 \x01(\x05\x12\x17\n\x0f\x61\x63tive_sessions\x18\x04 \x01(\x05\x12,\n\x07\x63lients\x18\x05 \x03(\x0b\x32\x1b.abbenay.v1.ConnectedClient\x12\x1e\n\x16registered_mcp_servers\x18\x06 \x03(\t\"\xaa\x01\n\x0f\x43onnectedClient\x12\x11\n\tclient_id\x18\x01 \x01(\t\x12+\n\x0b\x63lient_type\x18\x02 \x01(\x0e\x32\x16.abbenay.v1.ClientType\x12+\n\x0c\x63onnected_at\x18\x03 \x01(\x0b\x32\x15.abbenay.v1.Timestamp\x12\x12\n\nis_spawner\x18\x04 \x01(\x08\x12\x16\n\x0eworkspace_path\x18\x05 \x01(\t\">\n\x0fShutdownRequest\x12\r\n\x05\x66orce\x18\x01 \x01(\x08\x12\x1c\n\x14grace_period_seconds\x18\x02 \x01(\x05\"\x14\n\x12HealthCheckRequest\"7\n\x13HealthCheckResponse\x12\x0f\n\x07healthy\x18\x01 \x01(\x08\x12\x0f\n\x07version\x18\x02 \x01(\t\"%\n\x15StartWebServerRequest\x12\x0c\n\x04port\x18\x01 \x01(\x05\"]\n\x16StartWebServerResponse\x12\x0f\n\x07started\x18\x01 \x01(\x08\x12\x17\n\x0f\x61lready_running\x18\x02 \x01(\x08\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\x0b\n\x03url\x18\x04 \x01(\t\"\x16\n\x14StopWebServerRequest\"\x8d\x03\n\rVSCodeRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x34\n\x0binvoke_tool\x18\x02 \x01(\x0b\x32\x1d.abbenay.v1.InvokeToolRequestH\x00\x12:\n\x0blist_models\x18\x03 \x01(\x0b\x32#.abbenay.v1.ListVSCodeModelsRequestH\x00\x12\x36\n\tsend_chat\x18\x04 \x01(\x0b\x32!.abbenay.v1.SendVSCodeChatRequestH\x00\x12\x38\n\rget_workspace\x18\x06 \x01(\x0b\x32\x1f.abbenay.v1.GetWorkspaceRequestH\x00\x12?\n\x0emodels_changed\x18\x07 \x01(\x0b\x32%.abbenay.v1.ModelsChangedNotificationH\x00\x12\x38\n\nlist_tools\x18\x08 \x01(\x0b\x32\".abbenay.v1.ListVSCodeToolsRequestH\x00\x42\t\n\x07request\"+\n\x19ModelsChangedNotification\x12\x0e\n\x06reason\x18\x01 \x01(\t\"\xbe\x03\n\x0eVSCodeResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x35\n\x0binvoke_tool\x18\x02 \x01(\x0b\x32\x1e.abbenay.v1.InvokeToolResponseH\x00\x12;\n\x0blist_models\x18\x03 \x01(\x0b\x32$.abbenay.v1.ListVSCodeModelsResponseH\x00\x12\x37\n\tsend_chat\x18\x04 \x01(\x0b\x32\".abbenay.v1.SendVSCodeChatResponseH\x00\x12(\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x17.abbenay.v1.VSCodeErrorH\x00\x12\x39\n\rget_workspace\x18\x07 \x01(\x0b\x32 .abbenay.v1.GetWorkspaceResponseH\x00\x12\x39\n\nlist_tools\x18\x08 \x01(\x0b\x32#.abbenay.v1.ListVSCodeToolsResponseH\x00\x12?\n\x0eregister_tools\x18\t \x01(\x0b\x32%.abbenay.v1.RegisterToolsNotificationH\x00\x42\n\n\x08response\"\x15\n\x13GetWorkspaceRequest\"I\n\x14GetWorkspaceResponse\x12\x16\n\x0eworkspace_path\x18\x01 \x01(\t\x12\x19\n\x11workspace_folders\x18\x02 \x03(\t\",\n\x0bVSCodeError\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\">\n\x11InvokeToolRequest\x12\x11\n\ttool_name\x18\x01 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x02 \x01(\t\";\n\x12InvokeToolResponse\x12\x13\n\x0bresult_json\x18\x01 \x01(\t\x12\x10\n\x08is_error\x18\x02 \x01(\x08\"\x18\n\x16ListVSCodeToolsRequest\"D\n\x17ListVSCodeToolsResponse\x12)\n\x05tools\x18\x01 \x03(\x0b\x32\x1a.abbenay.v1.VSCodeToolInfo\"F\n\x19RegisterToolsNotification\x12)\n\x05tools\x18\x01 \x03(\x0b\x32\x1a.abbenay.v1.VSCodeToolInfo\"W\n\x0eVSCodeToolInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x14\n\x0cinput_schema\x18\x03 \x01(\t\x12\x0c\n\x04tags\x18\x04 \x03(\t\"G\n\x17ListVSCodeModelsRequest\x12\x1a\n\rfamily_filter\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\x10\n\x0e_family_filter\"C\n\x18ListVSCodeModelsResponse\x12\'\n\x06models\x18\x01 \x03(\x0b\x32\x17.abbenay.v1.VSCodeModel\"a\n\x0bVSCodeModel\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06vendor\x18\x03 \x01(\t\x12\x0e\n\x06\x66\x61mily\x18\x04 \x01(\t\x12\x18\n\x10max_input_tokens\x18\x05 \x01(\x05\"\xa2\x01\n\x15SendVSCodeChatRequest\x12\x10\n\x08model_id\x18\x01 \x01(\t\x12%\n\x08messages\x18\x02 \x03(\x0b\x32\x13.abbenay.v1.Message\x12\x18\n\x0btemperature\x18\x03 \x01(\x02H\x00\x88\x01\x01\x12\x17\n\nmax_tokens\x18\x04 \x01(\x05H\x01\x88\x01\x01\x42\x0e\n\x0c_temperatureB\r\n\x0b_max_tokens\"E\n\x16SendVSCodeChatResponse\x12+\n\x06\x63hunks\x18\x01 \x03(\x0b\x32\x1b.abbenay.v1.VSCodeChatChunk\"[\n\x0fVSCodeChatChunk\x12\x0e\n\x04text\x18\x01 \x01(\tH\x00\x12/\n\ttool_call\x18\x02 \x01(\x0b\x32\x1a.abbenay.v1.VSCodeToolCallH\x00\x42\x07\n\x05\x63hunk\"G\n\x0eVSCodeToolCall\x12\x0f\n\x07\x63\x61ll_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x16\n\x0e\x61rguments_json\x18\x03 \x01(\t\"\x15\n\x13ListPoliciesRequest\"@\n\x14ListPoliciesResponse\x12(\n\x08policies\x18\x01 \x03(\x0b\x32\x16.abbenay.v1.PolicyInfo\"U\n\nPolicyInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07\x62uiltin\x18\x02 \x01(\x08\x12(\n\x06\x63onfig\x18\x03 \x01(\x0b\x32\x18.abbenay.v1.PolicyConfig\"\xc2\x02\n\x0cPolicyConfig\x12\x31\n\x08sampling\x18\x01 \x01(\x0b\x32\x1a.abbenay.v1.PolicySamplingH\x00\x88\x01\x01\x12-\n\x06output\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.PolicyOutputH\x01\x88\x01\x01\x12/\n\x07\x63ontext\x18\x03 \x01(\x0b\x32\x19.abbenay.v1.PolicyContextH\x02\x88\x01\x01\x12)\n\x04tool\x18\x04 \x01(\x0b\x32\x16.abbenay.v1.PolicyToolH\x03\x88\x01\x01\x12\x37\n\x0breliability\x18\x05 \x01(\x0b\x32\x1d.abbenay.v1.PolicyReliabilityH\x04\x88\x01\x01\x42\x0b\n\t_samplingB\t\n\x07_outputB\n\n\x08_contextB\x07\n\x05_toolB\x0e\n\x0c_reliability\"v\n\x0ePolicySampling\x12\x18\n\x0btemperature\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12\x12\n\x05top_p\x18\x02 \x01(\x02H\x01\x88\x01\x01\x12\x12\n\x05top_k\x18\x03 \x01(\x05H\x02\x88\x01\x01\x42\x0e\n\x0c_temperatureB\x08\n\x06_top_pB\x08\n\x06_top_k\"\x8c\x02\n\x0cPolicyOutput\x12\x17\n\nmax_tokens\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12#\n\x16reserved_output_tokens\x18\x02 \x01(\x05H\x01\x88\x01\x01\x12\x13\n\x06\x66ormat\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\"\n\x15system_prompt_snippet\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x1f\n\x12system_prompt_mode\x18\x05 \x01(\tH\x04\x88\x01\x01\x42\r\n\x0b_max_tokensB\x19\n\x17_reserved_output_tokensB\t\n\x07_formatB\x18\n\x16_system_prompt_snippetB\x15\n\x13_system_prompt_mode\"\x81\x01\n\rPolicyContext\x12\x1e\n\x11\x63ontext_threshold\x18\x01 \x01(\x02H\x00\x88\x01\x01\x12!\n\x14\x63ompression_strategy\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x14\n\x12_context_thresholdB\x17\n\x15_compression_strategy\"l\n\nPolicyTool\x12 \n\x13max_tool_iterations\x18\x01 \x01(\x05H\x00\x88\x01\x01\x12\x16\n\ttool_mode\x18\x02 \x01(\tH\x01\x88\x01\x01\x42\x16\n\x14_max_tool_iterationsB\x0c\n\n_tool_mode\"s\n\x11PolicyReliability\x12\"\n\x15retry_on_invalid_json\x18\x01 \x01(\x08H\x00\x88\x01\x01\x12\x14\n\x07timeout\x18\x02 \x01(\x05H\x01\x88\x01\x01\x42\x18\n\x16_retry_on_invalid_jsonB\n\n\x08_timeout\"\xcd\x01\n\x18RegisterMcpServerRequest\x12\x11\n\tserver_id\x18\x01 \x01(\t\x12+\n\ttransport\x18\x02 \x01(\x0b\x32\x18.abbenay.v1.McpTransport\x12\x17\n\nsession_id\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x13\n\x0btool_filter\x18\x04 \x03(\t\x12\x1e\n\x11max_response_size\x18\x05 \x01(\x05H\x01\x88\x01\x01\x42\r\n\x0b_session_idB\x14\n\x12_max_response_size\"\xaa\x02\n\x0cMcpTransport\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x14\n\x07\x63ommand\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0c\n\x04\x61rgs\x18\x03 \x03(\t\x12\x10\n\x03url\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x36\n\x07headers\x18\x05 \x03(\x0b\x32%.abbenay.v1.McpTransport.HeadersEntry\x12.\n\x03\x65nv\x18\x06 \x03(\x0b\x32!.abbenay.v1.McpTransport.EnvEntry\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\n\n\x08_commandB\x06\n\x04_url\"U\n\x19RegisterMcpServerResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x18\n\x10\x64iscovered_tools\x18\x03 \x03(\t\"/\n\x1aUnregisterMcpServerRequest\x12\x11\n\tserver_id\x18\x01 \x01(\t\".\n\x1bUnregisterMcpServerResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08*_\n\x04Role\x12\x14\n\x10ROLE_UNSPECIFIED\x10\x00\x12\x0f\n\x0bROLE_SYSTEM\x10\x01\x12\r\n\tROLE_USER\x10\x02\x12\x12\n\x0eROLE_ASSISTANT\x10\x03\x12\r\n\tROLE_TOOL\x10\x04*\x9b\x01\n\nClientType\x12\x1b\n\x17\x43LIENT_TYPE_UNSPECIFIED\x10\x00\x12\x16\n\x12\x43LIENT_TYPE_VSCODE\x10\x01\x12\x13\n\x0f\x43LIENT_TYPE_CLI\x10\x02\x12\x16\n\x12\x43LIENT_TYPE_PYTHON\x10\x03\x12\x16\n\x12\x43LIENT_TYPE_NODEJS\x10\x04\x12\x13\n\x0f\x43LIENT_TYPE_MCP\x10\x05*\x82\x01\n\x0cReplayFormat\x12\x1d\n\x19REPLAY_FORMAT_UNSPECIFIED\x10\x00\x12\x16\n\x12REPLAY_FORMAT_FULL\x10\x01\x12\x1b\n\x17REPLAY_FORMAT_CONDENSED\x10\x02\x12\x1e\n\x1aREPLAY_FORMAT_SUMMARY_ONLY\x10\x03*`\n\x0bModelSource\x12\x1c\n\x18MODEL_SOURCE_UNSPECIFIED\x10\x00\x12\x17\n\x13MODEL_SOURCE_DIRECT\x10\x01\x12\x1a\n\x16MODEL_SOURCE_VSCODE_LM\x10\x02*u\n\x0cProviderType\x12\x1d\n\x19PROVIDER_TYPE_UNSPECIFIED\x10\x00\x12\x16\n\x12PROVIDER_TYPE_HTTP\x10\x01\x12\x15\n\x11PROVIDER_TYPE_MCP\x10\x02\x12\x17\n\x13PROVIDER_TYPE_LOCAL\x10\x03*\x8c\x01\n\x08LogLevel\x12\x19\n\x15LOG_LEVEL_UNSPECIFIED\x10\x00\x12\x13\n\x0fLOG_LEVEL_ERROR\x10\x01\x12\x12\n\x0eLOG_LEVEL_WARN\x10\x02\x12\x12\n\x0eLOG_LEVEL_INFO\x10\x03\x12\x13\n\x0fLOG_LEVEL_DEBUG\x10\x04\x12\x13\n\x0fLOG_LEVEL_TRACE\x10\x05*\x8c\x01\n\x0bSecretStore\x12\x1c\n\x18SECRET_STORE_UNSPECIFIED\x10\x00\x12\x19\n\x15SECRET_STORE_KEYCHAIN\x10\x01\x12\x14\n\x10SECRET_STORE_ENV\x10\x02\x12\x17\n\x13SECRET_STORE_MEMORY\x10\x03\x12\x15\n\x11SECRET_STORE_FILE\x10\x04\x32\x9f\x05\n\tMcpBridge\x12Q\n\x0cListMcpTools\x12\x1f.abbenay.v1.ListMcpToolsRequest\x1a .abbenay.v1.ListMcpToolsResponse\x12H\n\x08\x43\x61llTool\x12\x1b.abbenay.v1.CallToolRequest\x1a\x1d.abbenay.v1.ToolResponseChunk0\x01\x12\\\n\x13\x43\x61llToolInteractive\x12\".abbenay.v1.ToolInteractiveRequest\x1a\x1d.abbenay.v1.ToolResponseChunk(\x01\x30\x01\x12T\n\x0e\x43\x61llToolsBatch\x12!.abbenay.v1.CallToolsBatchRequest\x1a\x1d.abbenay.v1.ToolResponseChunk0\x01\x12T\n\rListResources\x12 .abbenay.v1.ListResourcesRequest\x1a!.abbenay.v1.ListResourcesResponse\x12Q\n\x0cReadResource\x12\x1f.abbenay.v1.ReadResourceRequest\x1a .abbenay.v1.ReadResourceResponse\x12N\n\x0bListPrompts\x12\x1e.abbenay.v1.ListPromptsRequest\x1a\x1f.abbenay.v1.ListPromptsResponse\x12H\n\tGetPrompt\x12\x1c.abbenay.v1.GetPromptRequest\x1a\x1d.abbenay.v1.GetPromptResponse2\x82\x1c\n\x07\x41\x62\x62\x65nay\x12\x38\n\x04\x43hat\x12\x17.abbenay.v1.ChatRequest\x1a\x15.abbenay.v1.ChatChunk0\x01\x12\x46\n\x0bSessionChat\x12\x1e.abbenay.v1.SessionChatRequest\x1a\x15.abbenay.v1.ChatChunk0\x01\x12\x46\n\rCreateSession\x12 .abbenay.v1.CreateSessionRequest\x1a\x13.abbenay.v1.Session\x12@\n\nGetSession\x12\x1d.abbenay.v1.GetSessionRequest\x1a\x13.abbenay.v1.Session\x12Q\n\x0cListSessions\x12\x1f.abbenay.v1.ListSessionsRequest\x1a .abbenay.v1.ListSessionsResponse\x12\x44\n\rDeleteSession\x12 .abbenay.v1.DeleteSessionRequest\x1a\x11.abbenay.v1.Empty\x12M\n\rWatchSessions\x12 .abbenay.v1.WatchSessionsRequest\x1a\x18.abbenay.v1.SessionEvent0\x01\x12T\n\rReplaySession\x12 .abbenay.v1.ReplaySessionRequest\x1a!.abbenay.v1.ReplaySessionResponse\x12]\n\x10SummarizeSession\x12#.abbenay.v1.SummarizeSessionRequest\x1a$.abbenay.v1.SummarizeSessionResponse\x12\x42\n\x0b\x46orkSession\x12\x1e.abbenay.v1.ForkSessionRequest\x1a\x13.abbenay.v1.Session\x12T\n\rExportSession\x12 .abbenay.v1.ExportSessionRequest\x1a!.abbenay.v1.ExportSessionResponse\x12\x46\n\rImportSession\x12 .abbenay.v1.ImportSessionRequest\x1a\x13.abbenay.v1.Session\x12K\n\nListModels\x12\x1d.abbenay.v1.ListModelsRequest\x1a\x1e.abbenay.v1.ListModelsResponse\x12W\n\x0e\x44iscoverModels\x12!.abbenay.v1.DiscoverModelsRequest\x1a\".abbenay.v1.DiscoverModelsResponse\x12T\n\rListProviders\x12 .abbenay.v1.ListProvidersRequest\x1a!.abbenay.v1.ListProvidersResponse\x12U\n\x11GetProviderStatus\x12$.abbenay.v1.GetProviderStatusRequest\x1a\x1a.abbenay.v1.ProviderStatus\x12N\n\x0bListEngines\x12\x1e.abbenay.v1.ListEnginesRequest\x1a\x1f.abbenay.v1.ListEnginesResponse\x12`\n\x11\x43onfigureProvider\x12$.abbenay.v1.ConfigureProviderRequest\x1a%.abbenay.v1.ConfigureProviderResponse\x12\x46\n\x0eRemoveProvider\x12!.abbenay.v1.RemoveProviderRequest\x1a\x11.abbenay.v1.Empty\x12i\n\x14GetProviderTemplates\x12\'.abbenay.v1.GetProviderTemplatesRequest\x1a(.abbenay.v1.GetProviderTemplatesResponse\x12Q\n\x0cListPolicies\x12\x1f.abbenay.v1.ListPoliciesRequest\x1a .abbenay.v1.ListPoliciesResponse\x12\x42\n\x0c\x43reatePolicy\x12\x1f.abbenay.v1.CreatePolicyRequest\x1a\x11.abbenay.v1.Empty\x12\x42\n\x0c\x44\x65letePolicy\x12\x1f.abbenay.v1.DeletePolicyRequest\x1a\x11.abbenay.v1.Empty\x12H\n\tListTools\x12\x1c.abbenay.v1.ListToolsRequest\x1a\x1d.abbenay.v1.ListToolsResponse\x12N\n\x0b\x45xecuteTool\x12\x1e.abbenay.v1.ExecuteToolRequest\x1a\x1f.abbenay.v1.ExecuteToolResponse\x12H\n\tGetConfig\x12\x1c.abbenay.v1.GetConfigRequest\x1a\x1d.abbenay.v1.GetConfigResponse\x12N\n\x0cUpdateConfig\x12\x1f.abbenay.v1.UpdateConfigRequest\x1a\x1d.abbenay.v1.GetConfigResponse\x12Q\n\x0cGetKeyStatus\x12\x1f.abbenay.v1.GetKeyStatusRequest\x1a .abbenay.v1.GetKeyStatusResponse\x12i\n\x14ListMcpServerConfigs\x12\'.abbenay.v1.ListMcpServerConfigsRequest\x1a(.abbenay.v1.ListMcpServerConfigsResponse\x12N\n\x12ReconnectMcpServer\x12%.abbenay.v1.ReconnectMcpServerRequest\x1a\x11.abbenay.v1.Empty\x12H\n\tGetSecret\x12\x1c.abbenay.v1.GetSecretRequest\x1a\x1d.abbenay.v1.GetSecretResponse\x12<\n\tSetSecret\x12\x1c.abbenay.v1.SetSecretRequest\x1a\x11.abbenay.v1.Empty\x12\x42\n\x0c\x44\x65leteSecret\x12\x1f.abbenay.v1.DeleteSecretRequest\x1a\x11.abbenay.v1.Empty\x12N\n\x0bListSecrets\x12\x1e.abbenay.v1.ListSecretsRequest\x1a\x1f.abbenay.v1.ListSecretsResponse\x12\x45\n\x08Register\x12\x1b.abbenay.v1.RegisterRequest\x1a\x1c.abbenay.v1.RegisterResponse\x12>\n\nUnregister\x12\x1d.abbenay.v1.UnregisterRequest\x1a\x11.abbenay.v1.Empty\x12\x43\n\tGetStatus\x12\x1c.abbenay.v1.GetStatusRequest\x1a\x18.abbenay.v1.DaemonStatus\x12o\n\x16GetConnectedWorkspaces\x12).abbenay.v1.GetConnectedWorkspacesRequest\x1a*.abbenay.v1.GetConnectedWorkspacesResponse\x12:\n\x08Shutdown\x12\x1b.abbenay.v1.ShutdownRequest\x1a\x11.abbenay.v1.Empty\x12N\n\x0bHealthCheck\x12\x1e.abbenay.v1.HealthCheckRequest\x1a\x1f.abbenay.v1.HealthCheckResponse\x12W\n\x0eStartWebServer\x12!.abbenay.v1.StartWebServerRequest\x1a\".abbenay.v1.StartWebServerResponse\x12\x44\n\rStopWebServer\x12 .abbenay.v1.StopWebServerRequest\x1a\x11.abbenay.v1.Empty\x12`\n\x11RegisterMcpServer\x12$.abbenay.v1.RegisterMcpServerRequest\x1a%.abbenay.v1.RegisterMcpServerResponse\x12\x66\n\x13UnregisterMcpServer\x12&.abbenay.v1.UnregisterMcpServerRequest\x1a\'.abbenay.v1.UnregisterMcpServerResponse\x12I\n\x0cVSCodeStream\x12\x1a.abbenay.v1.VSCodeResponse\x1a\x19.abbenay.v1.VSCodeRequest(\x01\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -69,8 +69,8 @@ _globals['_PROVIDERTYPE']._serialized_end=19119 _globals['_LOGLEVEL']._serialized_start=19122 _globals['_LOGLEVEL']._serialized_end=19262 - _globals['_SECRETSTORE']._serialized_start=19264 - _globals['_SECRETSTORE']._serialized_end=19381 + _globals['_SECRETSTORE']._serialized_start=19265 + _globals['_SECRETSTORE']._serialized_end=19405 _globals['_EMPTY']._serialized_start=40 _globals['_EMPTY']._serialized_end=47 _globals['_TIMESTAMP']._serialized_start=49 @@ -421,8 +421,8 @@ _globals['_UNREGISTERMCPSERVERREQUEST']._serialized_end=18466 _globals['_UNREGISTERMCPSERVERRESPONSE']._serialized_start=18468 _globals['_UNREGISTERMCPSERVERRESPONSE']._serialized_end=18514 - _globals['_MCPBRIDGE']._serialized_start=19384 - _globals['_MCPBRIDGE']._serialized_end=20055 - _globals['_ABBENAY']._serialized_start=20058 - _globals['_ABBENAY']._serialized_end=23644 + _globals['_MCPBRIDGE']._serialized_start=19408 + _globals['_MCPBRIDGE']._serialized_end=20079 + _globals['_ABBENAY']._serialized_start=20082 + _globals['_ABBENAY']._serialized_end=23668 # @@protoc_insertion_point(module_scope) diff --git a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.pyi b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.pyi index c55dbe5b..20f39804 100644 --- a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.pyi +++ b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.pyi @@ -59,6 +59,7 @@ class SecretStore(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): SECRET_STORE_KEYCHAIN: _ClassVar[SecretStore] SECRET_STORE_ENV: _ClassVar[SecretStore] SECRET_STORE_MEMORY: _ClassVar[SecretStore] + SECRET_STORE_FILE: _ClassVar[SecretStore] ROLE_UNSPECIFIED: Role ROLE_SYSTEM: Role ROLE_USER: Role @@ -91,6 +92,7 @@ SECRET_STORE_UNSPECIFIED: SecretStore SECRET_STORE_KEYCHAIN: SecretStore SECRET_STORE_ENV: SecretStore SECRET_STORE_MEMORY: SecretStore +SECRET_STORE_FILE: SecretStore class Empty(_message.Message): __slots__ = () diff --git a/packages/vscode/src/proto/abbenay/v1/service.ts b/packages/vscode/src/proto/abbenay/v1/service.ts index a69bcb11..7d0e289d 100644 --- a/packages/vscode/src/proto/abbenay/v1/service.ts +++ b/packages/vscode/src/proto/abbenay/v1/service.ts @@ -319,6 +319,8 @@ export enum SecretStore { SECRET_STORE_KEYCHAIN = 1, SECRET_STORE_ENV = 2, SECRET_STORE_MEMORY = 3, + /** SECRET_STORE_FILE - config-dir secrets.json (durable with volume) */ + SECRET_STORE_FILE = 4, UNRECOGNIZED = -1, } @@ -336,6 +338,9 @@ export function secretStoreFromJSON(object: any): SecretStore { case 3: case "SECRET_STORE_MEMORY": return SecretStore.SECRET_STORE_MEMORY; + case 4: + case "SECRET_STORE_FILE": + return SecretStore.SECRET_STORE_FILE; case -1: case "UNRECOGNIZED": default: @@ -353,6 +358,8 @@ export function secretStoreToJSON(object: SecretStore): string { return "SECRET_STORE_ENV"; case SecretStore.SECRET_STORE_MEMORY: return "SECRET_STORE_MEMORY"; + case SecretStore.SECRET_STORE_FILE: + return "SECRET_STORE_FILE"; case SecretStore.UNRECOGNIZED: default: return "UNRECOGNIZED"; diff --git a/proto/abbenay/v1/service.proto b/proto/abbenay/v1/service.proto index 024dfcdb..b0d78bca 100644 --- a/proto/abbenay/v1/service.proto +++ b/proto/abbenay/v1/service.proto @@ -1040,6 +1040,7 @@ enum SecretStore { SECRET_STORE_KEYCHAIN = 1; SECRET_STORE_ENV = 2; SECRET_STORE_MEMORY = 3; + SECRET_STORE_FILE = 4; // config-dir secrets.json (durable with volume) } message DeleteSecretRequest { diff --git a/sonar-project.properties b/sonar-project.properties index 14f07bb1..55802ce6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -19,5 +19,7 @@ sonar.tests=packages/daemon/src/,packages/daemon/tests/,packages/vscode/src/test sonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/**,packages/proto-ts/**,packages/vscode/src/proto/**,packages/python/**,**/*.test.ts sonar.test.inclusions=**/*.test.ts # Test helpers and generated stubs must never count toward coverage gates. -sonar.coverage.exclusions=**/*.test.ts,**/tests/helpers/**,packages/vscode/src/proto/** +# VS Code extension sources are analyzed for issues, but CI only uploads +# packages/daemon/coverage/lcov.info — do not count vscode toward coverage %. +sonar.coverage.exclusions=**/*.test.ts,**/tests/helpers/**,packages/vscode/** sonar.verbose=false