diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c09ac4e7..a7896ddc 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -32,7 +32,7 @@ Abbenay is a unified AI daemon and library written in TypeScript/Node.js that pr │ │ │ ┌─ daemon layer ────────────────────────────────────────────────────┐ │ │ │ DaemonState gRPC Server VS Code Backchannel │ │ -│ │ CLI (Commander) Web Dashboard (Express) KeychainSecretStore │ │ +│ │ CLI (Commander) Web Dashboard (Express) SecretStoreRegistry │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────┬──────────────────────────────┘ @@ -87,6 +87,7 @@ 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/keychain.ts` | `KeychainSecretStore` (keytar native addon) | ## Components @@ -184,7 +185,8 @@ Secrets are managed explicitly per-provider with two options: - macOS: Keychain - Linux: libsecret (GNOME Keyring / KDE Wallet) - Windows: Credential Vault -- Config references key by name: `api_key_keychain_name: "OPENAI_API_KEY"` +- Config references secret by name: `secret_name: "OPENAI_API_KEY"` + (legacy: `api_key_keychain_name`) ### Option 2: Environment Variable Reference - Config specifies env var name: `api_key_env_var_name: "OPENAI_API_KEY"` @@ -203,7 +205,12 @@ interface SecretStore { } ``` -`CoreState` accepts any `SecretStore` via constructor injection. `DaemonState` uses `KeychainSecretStore` (keytar-backed) by default. Tests and library consumers can use `MemorySecretStore`. +`CoreState` accepts any `SecretStore` via constructor injection. `DaemonState` +uses `SecretStoreRegistry` (discrete process-lifetime `MemorySecretStore` + +keytar-backed `KeychainSecretStore` 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` / … ## Configuration diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 2533ca1a..951b6df6 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -382,35 +382,59 @@ Legacy sessions without an `owner` field are treated as `local`. Each provider can specify exactly ONE of these (mutually exclusive): -### Option 1: Keychain Storage (`api_key_keychain_name`) +### Option 1: Secret store (`secret_name` + `secret_store`) -- Key stored in system keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service) -- Specify the key name used in keychain -- Set via web dashboard "API Key" toggle +- `secret_name` is the logical name within a backend +- `secret_store` selects the namespace: + - `keychain` (default when omitted) → OS keychain + - `memory` → process-lifetime daemon memory + - `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) +- Legacy aliases: `api_key_keychain_name`, `api_key_env_var_name` ```yaml providers: my-openai: engine: openai - api_key_keychain_name: "OPENAI_API_KEY" + secret_name: "OPENAI_API_KEY" + secret_store: keychain # optional; defaults to keychain ``` -### Option 2: Environment Variable (`api_key_env_var_name`) - -- Key read from environment variable at runtime -- Specify the env var name to check -- Set via web dashboard "Env" toggle - ```yaml providers: - anthropic-work: - engine: anthropic - api_key_env_var_name: "ANTHROPIC_API_KEY" + ci-openai: + engine: openai + secret_name: "OPENAI_API_KEY" + secret_store: env # already exported in the process environment ``` +### Option 1b: In-memory (process-lifetime) via secrets API + +- Same `secret_name` in the `memory` namespace; set value with `SetSecret` / + `POST /api/secrets` and `secret_store: memory` (SetSecret still cannot + write `env`) +- Lives only while the daemon process is running +- Independent from keychain — overlapping names are allowed (DR-047) + +### Recommended workflow (keys are N:1 with providers) + +1. **Add a secret** (store) — `SetSecret` with `secret_store=memory|keychain`, **or** + export an env var yourself. +2. **Configure the provider** — `secret_name=NAME` and + `secret_store=memory|keychain|env`. 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. + +### Option 2: Environment Variable (legacy `api_key_env_var_name`) + +Prefer `secret_name` + `secret_store: env`. The legacy field still works and is +mirrored on load. + ### Fallback -If neither option is set, the engine's default environment variable is checked (e.g., `OPENAI_API_KEY` for the `openai` engine). +If neither option is set, the engine's default environment variable is checked +(e.g., `OPENAI_API_KEY` for the `openai` engine). ## Supported Engines @@ -713,7 +737,8 @@ steal prompts/responses/keys — constrained by the | Config files mode `0600` | User-only read/write on disk | | HTTP Bearer auth (DR-030) | Unauthenticated callers cannot read/write secrets or configure providers | | gRPC consumer capabilities (DR-037) | On non-localhost binds, sensitive RPCs require token + capability (`secrets`, `providers`, `config`, `mcp_register`, …) | -| Secret API shape | HTTP `GET /api/secrets` returns key names + `hasValue` only — never secret values. gRPC `GetSecret` (value-returning) requires the `secrets` capability | +| Secret API shape | HTTP `GET /api/secrets` returns key names + `hasValue` (+ `store` when present) only — never secret values. gRPC `GetSecret` (value-returning) requires the `secrets` capability | +| In-memory secrets (DR-047) | Optional process-lifetime backend; never written to disk/keychain; same auth gates as keychain secrets; vanishes on daemon restart | | Secret / endpoint audit logs | `[Audit] secret changed` and `[Audit] provider endpoint changed` (never log secret values) | | Provider endpoint policy (DR-040) | Malformed / disallowed `base_url` values are rejected; changes are audited | | Localhost bind defaults | HTTP/gRPC default to loopback; non-loopback requires intentional opt-in | diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 473d3e13..6ef59b08 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -65,6 +65,11 @@ defaults (or stronger) in place. - `--host 0.0.0.0`, `ABBENAY_HTTP_AUTH=0`, and `--insecure` deliberately weaken posture. - Cloud providers still receive prompts if you configure them. - HTTP on loopback remains plaintext; use a TLS-terminating proxy when exposing beyond the machine. +- In-memory secrets (DR-047) live in a discrete `memory` namespace (not written + to disk or the OS keychain). Providers must set `secret_store: memory` to + 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. --- diff --git a/docs/decisions.md b/docs/decisions.md index 8a018ec8..36f1bd6b 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -828,3 +828,36 @@ matching request tool with `400`. When tools mode is `off`, ignore `tool_choice` to force or suppress tool use; dropping it silently made passthrough incomplete versus a real OpenAI-compatible endpoint (issue #77). Numbered DR-046 because main already shipped VS Code webview UX as DR-045. + +--- + +## DR-047: Process-lifetime in-memory secret store + +**Date:** 2026-08-12 +**Decision:** The daemon exposes discrete secret backends via +`SecretStoreRegistry` (OS keychain + process-lifetime memory today). Values are +addressed by `(secret_store, secret_name)` — the same name may exist in more +than one backend. Clients select the backend on write via gRPC +`SetSecretRequest.store` (`SECRET_STORE_MEMORY` / `SECRET_STORE_KEYCHAIN`) or +HTTP `secret_store: "memory" | "keychain"` (default keychain). Get/Delete +take the same store field (default keychain). Provider config records both +`secret_name` and `secret_store`; resolve reads only that backend (no +cross-store overlay). Memory secrets survive until consumer delete/set or +daemon restart — no TTL and no clear-on-disconnect. `SECRET_STORE_ENV` is +rejected on write; env credentials are configure/reference only +(`secret_store: env` → `process.env[secret_name]`). `ConfigureProvider` +accepts `secret_name` + optional `secret_store` to reference an existing +secret (N providers → 1 key). Raw `api_key` alone remains a legacy shortcut +that invents `${PROVIDER_ID}_API_KEY`. Legacy `api_key_env_var_name` maps to +`secret_name` + `secret_store: env` on load; omitted store for store-backed +secrets defaults to `keychain`. `SecretStoreRegistry` + `NamespacedSecretStore` +(`getFrom` / `setIn` / …) is the extension surface for additional backends later. +**Rationale:** Ephemeral keys for clients that must not persist credentials +were already sketched (`MemorySecretStore`, proto `SECRET_STORE_MEMORY`) but +unused by the daemon. Discrete namespaces (not move-on-write exclusivity) +match provider config, which already carries `secret_store`, and position +the daemon for N pluggable backends without renaming. Defaulting unspecified +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. diff --git a/packages/daemon/src/core/config-schema.ts b/packages/daemon/src/core/config-schema.ts index 598ab0d5..aee7a5f3 100644 --- a/packages/daemon/src/core/config-schema.ts +++ b/packages/daemon/src/core/config-schema.ts @@ -83,7 +83,13 @@ export const ModelConfigSchema = z export const ProviderConfigSchema = z .object({ 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(), + /** @deprecated Use secret_name. */ api_key_keychain_name: z.string().min(1).optional(), + /** @deprecated Use secret_name + secret_store: env. */ api_key_env_var_name: z.string().min(1).optional(), base_url: ProviderBaseUrlSchema.optional(), models: z.record(z.string(), ModelConfigSchema).optional(), diff --git a/packages/daemon/src/core/config.test.ts b/packages/daemon/src/core/config.test.ts index 7b7bc056..54c0cdcf 100644 --- a/packages/daemon/src/core/config.test.ts +++ b/packages/daemon/src/core/config.test.ts @@ -31,6 +31,9 @@ import { getEnabledModelNames, isValidVirtualName, resolveEngineModelId, + providerSecretName, + isProviderOwnedSecretName, + providerCredentialSource, type ConfigFile, } from './config.js'; @@ -65,6 +68,73 @@ function createWorkspace(name: string, data: Record): string { // ── isValidVirtualName ──────────────────────────────────────────────────────── +describe('providerSecretName / isProviderOwnedSecretName / providerCredentialSource', () => { + it('prefers secret_name over legacy api_key_keychain_name', () => { + expect(providerSecretName({ engine: 'openai', secret_name: 'SHARED' })).toBe('SHARED'); + expect(providerSecretName({ + engine: 'openai', + secret_name: 'SHARED', + api_key_keychain_name: 'LEGACY', + })).toBe('SHARED'); + expect(providerSecretName({ + engine: 'openai', + api_key_keychain_name: 'LEGACY', + })).toBe('LEGACY'); + expect(providerSecretName({ engine: 'openai' })).toBeUndefined(); + }); + + it('recognizes only invented provider-owned secret names', () => { + expect(isProviderOwnedSecretName('work-openai', 'WORK-OPENAI_API_KEY')).toBe(true); + expect(isProviderOwnedSecretName('work-openai', 'abbenay.work-openai')).toBe(true); + expect(isProviderOwnedSecretName('work-openai', 'SHARED_OPENAI')).toBe(false); + expect(isProviderOwnedSecretName('work-openai', 'OPENAI_API_KEY')).toBe(false); + }); + + it('resolves store backends with keychain default and memory override', () => { + expect(providerCredentialSource({ + engine: 'openai', + secret_name: 'K', + })).toEqual({ kind: 'store', name: 'K', backend: 'keychain' }); + + expect(providerCredentialSource({ + engine: 'openai', + secret_name: 'K', + secret_store: 'memory', + })).toEqual({ kind: 'store', name: 'K', backend: 'memory' }); + + expect(providerCredentialSource({ + engine: 'openai', + secret_name: 'K', + secret_store: 'keychain', + })).toEqual({ kind: 'store', name: 'K', backend: 'keychain' }); + }); + + it('resolves env via secret_store=env or legacy api_key_env_var_name', () => { + expect(providerCredentialSource({ + engine: 'openai', + secret_name: 'MY_ENV', + secret_store: 'env', + })).toEqual({ kind: 'env', name: 'MY_ENV' }); + + expect(providerCredentialSource({ + engine: 'openai', + secret_store: 'env', + api_key_env_var_name: 'LEGACY_ENV', + })).toEqual({ kind: 'env', name: 'LEGACY_ENV' }); + + expect(providerCredentialSource({ + engine: 'openai', + api_key_env_var_name: 'ONLY_ENV', + })).toEqual({ kind: 'env', name: 'ONLY_ENV' }); + + expect(providerCredentialSource({ engine: 'openai' })).toBeNull(); + expect(providerCredentialSource({ + engine: 'openai', + secret_store: 'env', + })).toBeNull(); + }); +}); + describe('isValidVirtualName', () => { it('should accept simple lowercase names', () => { expect(isValidVirtualName('openrouter')).toBe(true); @@ -159,6 +229,39 @@ describe('loadConfigFromPath (new schema)', () => { }); expect(loadConfigFromPath(filePath)).toEqual({ providers: {} }); }); + + it('mirrors secret_name / env aliases and defaults secret_store', () => { + const filePath = writeYaml(path.join(tmpDir, 'secrets.yaml'), { + providers: { + named: { + engine: 'openai', + secret_name: 'SHARED_KEY', + models: {}, + }, + envlegacy: { + engine: 'openai', + api_key_env_var_name: 'ONLY_ENV', + models: {}, + }, + envin: { + engine: 'openai', + secret_name: 'MY_ENV', + secret_store: 'env', + models: {}, + }, + }, + }); + const config = loadConfigFromPath(filePath)!; + expect(config.providers!.named.secret_name).toBe('SHARED_KEY'); + expect(config.providers!.named.api_key_keychain_name).toBe('SHARED_KEY'); + expect(config.providers!.named.secret_store).toBe('keychain'); + + expect(config.providers!.envlegacy.secret_name).toBe('ONLY_ENV'); + expect(config.providers!.envlegacy.secret_store).toBe('env'); + + expect(config.providers!.envin.api_key_env_var_name).toBe('MY_ENV'); + expect(config.providers!.envin.secret_store).toBe('env'); + }); }); // ── loadConfigFromPath (old schema migration) ──────────────────────────────── diff --git a/packages/daemon/src/core/config.ts b/packages/daemon/src/core/config.ts index f6ede5fe..3e9418ae 100644 --- a/packages/daemon/src/core/config.ts +++ b/packages/daemon/src/core/config.ts @@ -73,9 +73,24 @@ export interface ModelConfig { export interface ProviderConfig { /** Actual engine type: "openrouter", "openai", "anthropic", etc. */ engine: string; - /** Keychain key name for API key */ + /** + * Logical secret name: secret-store key or env var name. + * Preferred over {@link api_key_keychain_name} / {@link api_key_env_var_name}. + */ + secret_name?: string; + /** + * Where {@link secret_name} is resolved from. + * - `memory` / `keychain` / omitted (with a store name): daemon secret store + * - `env`: process environment variable + */ + secret_store?: 'memory' | 'keychain' | 'env'; + /** + * @deprecated Use {@link secret_name}. Kept for existing YAML configs. + */ api_key_keychain_name?: string; - /** Environment variable name for API key */ + /** + * @deprecated Use {@link secret_name} + `secret_store: env`. + */ api_key_env_var_name?: string; /** Custom API base URL (falls back to engine default) */ base_url?: string; @@ -83,6 +98,48 @@ export interface ProviderConfig { models?: Record; } +/** Resolve the logical secret / env var name (secret_name preferred). */ +export function providerSecretName(cfg: ProviderConfig): string | undefined { + return cfg.secret_name || cfg.api_key_keychain_name || undefined; +} + +/** + * True when `secretName` was invented for this provider (legacy 1:1 configure). + * Shared / explicitly picked names must not be deleted with the provider. + */ +export function isProviderOwnedSecretName(providerId: string, secretName: string): boolean { + return ( + secretName === `${providerId.toUpperCase()}_API_KEY` || + secretName === `abbenay.${providerId}` + ); +} + +/** + * How to resolve credentials for a provider. + * `store` → named backend (memory|keychain) 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: 'env'; name: string } + | null { + if (cfg.secret_store === 'env') { + const name = cfg.secret_name || cfg.api_key_env_var_name; + return name ? { kind: 'env', name } : null; + } + const storeName = providerSecretName(cfg); + if (storeName) { + const backend = cfg.secret_store === 'memory' ? 'memory' : 'keychain'; + return { kind: 'store', name: storeName, backend }; + } + if (cfg.api_key_env_var_name) { + return { kind: 'env', name: cfg.api_key_env_var_name }; + } + return null; +} + /** * MCP server connection configuration. * Defines an external MCP server the daemon can connect to for tool aggregation. @@ -291,6 +348,26 @@ export function loadConfigFromPath(configPath: string): ConfigFile | null { * (backward compat with the old 1:1 mapping). */ function migrateProviderConfig(provId: string, cfg: Record): void { + // Prefer secret_name; mirror legacy api_key_keychain_name either direction. + if (cfg.secret_name && !cfg.api_key_keychain_name && cfg.secret_store !== 'env') { + cfg.api_key_keychain_name = cfg.secret_name; + } else if (cfg.api_key_keychain_name && !cfg.secret_name) { + cfg.secret_name = cfg.api_key_keychain_name; + } + + // Legacy env-only config → secret_name + secret_store: env + if (cfg.api_key_env_var_name && !cfg.secret_name && !cfg.api_key_keychain_name) { + cfg.secret_name = cfg.api_key_env_var_name; + if (!cfg.secret_store) cfg.secret_store = 'env'; + } else if (cfg.secret_store === 'env' && cfg.secret_name && !cfg.api_key_env_var_name) { + cfg.api_key_env_var_name = cfg.secret_name; + } + + // Store-backed secrets default to the keychain namespace when store is omitted. + if (cfg.secret_name && !cfg.secret_store) { + cfg.secret_store = 'keychain'; + } + // If already new format (has engine field), just rename api_base → base_url if (cfg.engine) { if (cfg.api_base && !cfg.base_url) { diff --git a/packages/daemon/src/core/index.ts b/packages/daemon/src/core/index.ts index c1f14bac..7b955077 100644 --- a/packages/daemon/src/core/index.ts +++ b/packages/daemon/src/core/index.ts @@ -29,8 +29,8 @@ export { CoreState } from './state.js'; export type { CoreStateOptions, AddProviderOptions } from './state.js'; /** Secret store — inject your own or use the built-in MemorySecretStore */ -export { MemorySecretStore, auditSecretChange } from './secrets.js'; -export type { SecretStore, SecretAuditEvent } from './secrets.js'; +export { MemorySecretStore, auditSecretChange, isNamespacedSecretStore } from './secrets.js'; +export type { SecretStore, NamespacedSecretStore, SecretAuditEvent } from './secrets.js'; // ─── Types: Providers & Models ────────────────────────────────────────── @@ -79,6 +79,7 @@ export type { ConfigFile } from './config.js'; /** Per-provider configuration block */ export type { ProviderConfig } from './config.js'; +export { providerSecretName, providerCredentialSource, isProviderOwnedSecretName } from './config.js'; /** Per-model configuration block */ export type { ModelConfig } from './config.js'; diff --git a/packages/daemon/src/core/secrets.ts b/packages/daemon/src/core/secrets.ts index fcacb488..f962a49f 100644 --- a/packages/daemon/src/core/secrets.ts +++ b/packages/daemon/src/core/secrets.ts @@ -2,7 +2,8 @@ * Secret store interface and in-memory implementation. * * Core consumers inject their own SecretStore implementation. - * The daemon uses KeychainSecretStore (keytar); tests use MemorySecretStore. + * The daemon uses SecretStoreRegistry (MemorySecretStore + KeychainSecretStore); + * 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 @@ -27,11 +28,35 @@ export interface SecretStore { has(key: string): Promise; } +/** + * Multi-backend secret store: values are addressed by (backend, key). + * Built-ins today are `memory` | `keychain`; custom ids come later. + * {@link SecretStore} methods map to the implementation's default backend. + */ +export interface NamespacedSecretStore extends SecretStore { + getFrom(backend: string, key: string): Promise; + setIn(backend: string, key: string, value: string): Promise; + deleteFrom(backend: string, key: string): Promise; + hasIn(backend: string, key: string): Promise; +} + +export function isNamespacedSecretStore( + store: SecretStore, +): store is NamespacedSecretStore { + const candidate = store as NamespacedSecretStore; + return ( + typeof candidate.getFrom === 'function' && + typeof candidate.setIn === 'function' && + typeof candidate.deleteFrom === 'function' && + typeof candidate.hasIn === 'function' + ); +} + export interface SecretAuditEvent { /** Secret key name only — never the value */ key: string; op: 'set' | 'delete'; - /** http-secrets | grpc-secrets | http-configure | grpc-configure | core-add */ + /** http-secrets | http-secrets-memory | grpc-secrets | grpc-secrets-memory | http-configure | grpc-configure | core-add */ source: string; actor?: string; } @@ -39,6 +64,8 @@ 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 */ export function auditSecretChange(event: SecretAuditEvent): void { const safeKey = sanitizeForLog(event.key); diff --git a/packages/daemon/src/core/state.test.ts b/packages/daemon/src/core/state.test.ts index 41c31646..69d22fbf 100644 --- a/packages/daemon/src/core/state.test.ts +++ b/packages/daemon/src/core/state.test.ts @@ -7,6 +7,7 @@ import type { ConfigFile } from './config.js'; import type { DiscoveredModel } from './engines.js'; import { MemorySecretStore } from './secrets.js'; import { ToolRegistry } from './tool-registry.js'; +import type { NamespacedSecretStore } from './secrets.js'; // ── Partial engines mock (preserve real getEngine for addProvider policy tests) ── @@ -260,6 +261,68 @@ describe('CoreState.resolveApiKey', () => { expect(await core.resolveApiKey('openrouter')).toBe('sk-from-store'); }); + it('reads only the configured memory backend from a namespaced store', async () => { + const memory = new Map([['SHARED', 'from-memory']]); + const keychain = new Map([['SHARED', 'from-keychain']]); + const store: NamespacedSecretStore = { + get: async (key) => keychain.get(key) ?? null, + set: async (key, value) => { keychain.set(key, value); }, + delete: async (key) => keychain.delete(key), + has: async (key) => keychain.has(key), + getFrom: async (backend, key) => (backend === 'memory' ? memory : keychain).get(key) ?? null, + setIn: async (backend, key, value) => { (backend === 'memory' ? memory : keychain).set(key, value); }, + deleteFrom: async (backend, key) => (backend === 'memory' ? memory : keychain).delete(key), + hasIn: async (backend, key) => (backend === 'memory' ? memory : keychain).has(key), + }; + const core = createCore({ + secretStore: store, + config: { + providers: { + mem: { + engine: 'openrouter', + secret_name: 'SHARED', + secret_store: 'memory', + models: {}, + }, + kc: { + engine: 'openrouter', + secret_name: 'SHARED', + secret_store: 'keychain', + models: {}, + }, + }, + }, + }); + + expect(await core.resolveApiKey('mem')).toBe('from-memory'); + expect(await core.resolveApiKey('kc')).toBe('from-keychain'); + }); + + it('resolves secret_store=env without consulting the secret store', async () => { + const store = new MemorySecretStore(); + await store.set('SHOULD_NOT_USE', 'from-store'); + const core = createCore({ + secretStore: store, + config: { + providers: { + envp: { + engine: 'openrouter', + secret_name: 'ABBENAY_TEST_RESOLVE_ENV', + secret_store: 'env', + models: {}, + }, + }, + }, + }); + + process.env.ABBENAY_TEST_RESOLVE_ENV = 'from-env'; + try { + expect(await core.resolveApiKey('envp')).toBe('from-env'); + } finally { + delete process.env.ABBENAY_TEST_RESOLVE_ENV; + } + }); + it('falls back to configured env var then engine default env var', async () => { const core = createCore({ config: { diff --git a/packages/daemon/src/core/state.ts b/packages/daemon/src/core/state.ts index d5439275..3c6198ae 100644 --- a/packages/daemon/src/core/state.ts +++ b/packages/daemon/src/core/state.ts @@ -8,7 +8,7 @@ * (agent devs, web devs, custom apps). */ -import type { SecretStore } from './secrets.js'; +import { isNamespacedSecretStore, type SecretStore } from './secrets.js'; import { debug } from './debug.js'; import { loadConfig, @@ -19,6 +19,7 @@ import { type ConfigFile, type ProviderConfig, type ModelConfig, + providerCredentialSource, } from './config.js'; import { resolvePolicy, flattenPolicy, type FlattenedPolicy, type PolicyConfig } from './policies.js'; import { @@ -238,6 +239,7 @@ export class CoreState { if (options.apiKey) { const keychainName = `abbenay.${providerId}`; await this.secretStore.set(keychainName, options.apiKey); + providerCfg.secret_name = keychainName; providerCfg.api_key_keychain_name = keychainName; auditSecretChange({ key: keychainName, op: 'set', source: 'core-add' }); } else if (options.apiKeyEnvVar) { @@ -347,7 +349,7 @@ export class CoreState { } /** - * Resolve the API key for a virtual provider from config (keychain or env var). + * Resolve the API key for a virtual provider from secret store or env var. * Falls back to the engine's default env var if no explicit config. */ async resolveApiKey(providerId: string, providerCfg?: ProviderConfig): Promise { @@ -358,16 +360,15 @@ export class CoreState { if (!providerCfg) return null; - // Check keychain - if (providerCfg.api_key_keychain_name) { - const value = await this.secretStore.get(providerCfg.api_key_keychain_name); - if (value) return value; - } - - // Check env var from config - if (providerCfg.api_key_env_var_name) { - const value = process.env[providerCfg.api_key_env_var_name]; + const source = providerCredentialSource(providerCfg); + if (source?.kind === 'env') { + const value = process.env[source.name]; if (value && value.length > 0) return value; + } else if (source?.kind === 'store') { + const value = isNamespacedSecretStore(this.secretStore) + ? await this.secretStore.getFrom(source.backend, source.name) + : await this.secretStore.get(source.name); + if (value) return value; } // Fall back to engine's default env var diff --git a/packages/daemon/src/daemon/secrets/keychain.test.ts b/packages/daemon/src/daemon/secrets/keychain.test.ts index 92064a63..c9b203d1 100644 --- a/packages/daemon/src/daemon/secrets/keychain.test.ts +++ b/packages/daemon/src/daemon/secrets/keychain.test.ts @@ -272,10 +272,17 @@ describe('KeychainSecretStore', () => { it('short-circuits further loads after keytar import failure', async () => { const store = await loadStoreWithFailingKeytarImport(); + const internals = store as unknown as KeychainInternals; await expect(store.get('ONE')).resolves.toBeNull(); + expect(internals.keytar).toBeNull(); + expect(internals.loadError).toMatch(/keytar missing/); + const cachedError = internals.loadError; + await expect(store.get('TWO')).resolves.toBeNull(); - expect(warnSpy).toHaveBeenCalledTimes(1); + // Failure is sticky — no second import attempt / error rewrite. + expect(internals.loadError).toBe(cachedError); + expect(internals.keytar).toBeNull(); }); it('loads keytar from named export when default export lacks getPassword', async () => { diff --git a/packages/daemon/src/daemon/secrets/keychain.ts b/packages/daemon/src/daemon/secrets/keychain.ts index fdc8353c..d91def17 100644 --- a/packages/daemon/src/daemon/secrets/keychain.ts +++ b/packages/daemon/src/daemon/secrets/keychain.ts @@ -83,7 +83,8 @@ export class KeychainSecretStore implements SecretStore { if (!kt) return null; try { - return await kt.getPassword(SERVICE_NAME, key); + // keytar returns undefined for missing entries; SecretStore requires null. + return (await kt.getPassword(SERVICE_NAME, key)) ?? null; } catch (error: unknown) { const msg = error instanceof Error ? error.message : String(error); console.error(`[Secrets] Failed to get key '${key}':`, msg); diff --git a/packages/daemon/src/daemon/secrets/registry.test.ts b/packages/daemon/src/daemon/secrets/registry.test.ts new file mode 100644 index 00000000..57f329eb --- /dev/null +++ b/packages/daemon/src/daemon/secrets/registry.test.ts @@ -0,0 +1,148 @@ +/** + * Unit tests for SecretStoreRegistry (discrete namespaced backends). + */ + +import { describe, it, expect } from 'vitest'; +import { MemorySecretStore } from '../../core/secrets.js'; +import { + SecretStoreRegistry, + parseSecretStoreChoice, + requireNamespacedMemory, + 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 }; +} + +describe('SecretStoreRegistry', () => { + it('set defaults to keychain and leaves memory untouched', async () => { + const { registry, memory, keychain } = createRegistry(); + await memory.set('K', 'from-memory'); + await registry.set('K', 'from-keychain'); + + expect(await registry.get('K')).toBe('from-keychain'); + expect(await registry.getFrom('keychain', 'K')).toBe('from-keychain'); + expect(await registry.getFrom('memory', 'K')).toBe('from-memory'); + expect(await keychain.has('K')).toBe(true); + expect(await memory.has('K')).toBe(true); + expect(await registry.locateAll('K')).toEqual(['memory', 'keychain']); + }); + + it('setIn memory does not clear keychain (discrete namespaces)', async () => { + const { registry, memory, keychain } = createRegistry(); + await registry.setIn('keychain', 'K', 'persistent'); + await registry.setIn('memory', 'K', 'ephemeral'); + + expect(await memory.get('K')).toBe('ephemeral'); + expect(await keychain.get('K')).toBe('persistent'); + expect(await registry.getFrom('memory', 'K')).toBe('ephemeral'); + expect(await registry.getFrom('keychain', 'K')).toBe('persistent'); + }); + + it('SecretStore.get/has/delete map to keychain only', async () => { + const { registry, memory } = createRegistry(); + await registry.setIn('memory', 'K', 'mem'); + expect(await registry.get('K')).toBeNull(); + expect(await registry.has('K')).toBe(false); + + await registry.set('K', 'kc'); + expect(await registry.get('K')).toBe('kc'); + expect(await memory.get('K')).toBe('mem'); + + expect(await registry.delete('K')).toBe(true); + expect(await registry.has('K')).toBe(false); + expect(await memory.has('K')).toBe(true); + }); + + it('deleteFrom clears only the named backend', async () => { + const { registry, memory, keychain } = createRegistry(); + await registry.setIn('memory', 'K', '1'); + await registry.setIn('keychain', 'K', '2'); + expect(await registry.deleteFrom('memory', 'K')).toBe(true); + expect(await memory.has('K')).toBe(false); + expect(await keychain.has('K')).toBe(true); + }); + + it('hasIn and locateAll reflect each backend independently', async () => { + const { registry } = createRegistry(); + expect(await registry.locateAll('K')).toEqual([]); + expect(await registry.locate('K')).toBeNull(); + + await registry.setIn('memory', 'K', 'v'); + expect(await registry.hasIn('memory', 'K')).toBe(true); + expect(await registry.hasIn('keychain', 'K')).toBe(false); + expect(await registry.locateAll('K')).toEqual(['memory']); + + await registry.setIn('keychain', 'K', 'v2'); + expect(await registry.locateAll('K')).toEqual(['memory', 'keychain']); + }); + + it('rejects unknown backend ids', async () => { + const { registry } = createRegistry(); + await expect(registry.getFrom('vault', 'K')).rejects.toThrow(/Unknown secret store/); + }); +}); + +describe('parseSecretStoreChoice', () => { + it('defaults unspecified / keychain to keychain', () => { + for (const store of [undefined, null, '', 0, 'SECRET_STORE_UNSPECIFIED', 'keychain', 1]) { + expect(parseSecretStoreChoice(store)).toEqual({ ok: true, backend: 'keychain' }); + } + }); + + it('accepts memory', () => { + for (const store of ['memory', 'SECRET_STORE_MEMORY', 3]) { + expect(parseSecretStoreChoice(store)).toEqual({ ok: true, backend: 'memory' }); + } + }); + + it('rejects ENV writes', () => { + const result = parseSecretStoreChoice('SECRET_STORE_ENV'); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatch(/ENV|not writable|secrets API/i); + } + }); + + it('allows ENV when allowEnv is set (configure/reference)', () => { + expect(parseSecretStoreChoice('env', { allowEnv: true })).toEqual({ + ok: true, + backend: 'env', + }); + }); + + it('rejects unknown stores', () => { + const result = parseSecretStoreChoice('vault'); + expect(result.ok).toBe(false); + }); +}); + +describe('requireNamespacedMemory', () => { + it('allows memory when a registry is present', async () => { + const { MemorySecretStore } = await import('../../core/secrets.js'); + const registry = new SecretStoreRegistry(new MemorySecretStore(), new MemorySecretStore()); + expect(requireNamespacedMemory(registry, 'memory')).toEqual({ ok: true }); + expect(requireNamespacedMemory(registry, 'keychain')).toEqual({ ok: true }); + }); + + it('rejects memory when only a plain secret store is available', () => { + const result = requireNamespacedMemory(null, 'memory'); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatch(/memory|SecretStoreRegistry/i); + } + expect(requireNamespacedMemory(null, 'keychain')).toEqual({ ok: true }); + }); +}); + +describe('secretBackendToProto', () => { + it('maps backends to proto enum numbers', () => { + expect(secretBackendToProto('keychain')).toBe(1); + expect(secretBackendToProto('memory')).toBe(3); + expect(secretBackendToProto(null)).toBe(0); + }); +}); diff --git a/packages/daemon/src/daemon/secrets/registry.ts b/packages/daemon/src/daemon/secrets/registry.ts new file mode 100644 index 00000000..050e5923 --- /dev/null +++ b/packages/daemon/src/daemon/secrets/registry.ts @@ -0,0 +1,191 @@ +/** + * Registry of discrete secret backends (memory + keychain today). + * + * Each backend is independent — the same key name may exist in more than one. + * Resolution is always (secret_store, secret_name). The {@link SecretStore} + * interface methods map to the default backend (keychain) for backward + * compatibility; prefer {@link SecretStoreRegistry.getFrom} / {@link setIn} / + * {@link deleteFrom} / {@link hasIn} for explicit store selection. + * + * `env` is not registered here — it is a configure/resolve credential source + * (`process.env`), not a daemon-owned writable store. + */ + +import type { SecretStore } from '../../core/secrets.js'; + +/** Built-in writable registry backends. */ +export type SecretBackend = 'memory' | 'keychain'; + +/** Full secret_store vocabulary including env references. */ +export type SecretStoreKind = SecretBackend | 'env'; + +/** Default writable backend when store is omitted (backward compatible). */ +export const DEFAULT_SECRET_BACKEND: SecretBackend = 'keychain'; + +/** + * Maps store id → {@link SecretStore} implementation. + * + * - `set` / `get` / `has` / `delete` → {@link DEFAULT_SECRET_BACKEND} (keychain) + * - `setIn` / `getFrom` / `hasIn` / `deleteFrom` → explicit backend + * - `locateAll` → every backend that currently holds a key + */ +export class SecretStoreRegistry implements SecretStore { + constructor( + private readonly memory: SecretStore, + private readonly keychain: SecretStore, + ) {} + + /** Expose backends for tests and future registry iteration. */ + get memoryStore(): SecretStore { + return this.memory; + } + + get keychainStore(): SecretStore { + return this.keychain; + } + + /** Built-in writable backends (order stable for ListSecrets). */ + backends(): readonly SecretBackend[] { + return ['memory', 'keychain'] as const; + } + + private storeFor(backend: SecretBackend): SecretStore { + return backend === 'memory' ? this.memory : this.keychain; + } + + private requireBackend(backend: string): SecretBackend { + if (backend === 'memory' || backend === 'keychain') return backend; + throw new Error(`Unknown secret store: ${backend}`); + } + + /** SecretStore interface → default keychain namespace. */ + async get(key: string): Promise { + return this.getFrom(DEFAULT_SECRET_BACKEND, key); + } + + async set(key: string, value: string): Promise { + await this.setIn(DEFAULT_SECRET_BACKEND, key, value); + } + + async delete(key: string): Promise { + return this.deleteFrom(DEFAULT_SECRET_BACKEND, key); + } + + async has(key: string): Promise { + return this.hasIn(DEFAULT_SECRET_BACKEND, key); + } + + async getFrom(backend: string, key: string): Promise { + return this.storeFor(this.requireBackend(backend)).get(key); + } + + /** Write to one backend only — does not clear other namespaces. */ + async setIn(backend: string, key: string, value: string): Promise { + await this.storeFor(this.requireBackend(backend)).set(key, value); + } + + async deleteFrom(backend: string, key: string): Promise { + return this.storeFor(this.requireBackend(backend)).delete(key); + } + + async hasIn(backend: string, key: string): Promise { + return this.storeFor(this.requireBackend(backend)).has(key); + } + + /** Backends that currently hold `key` (may be empty, one, or many). */ + async locateAll(key: string): Promise { + const found: SecretBackend[] = []; + for (const backend of this.backends()) { + if (await this.hasIn(backend, key)) found.push(backend); + } + return found; + } + + /** + * @deprecated Prefer {@link locateAll}. Returns the first backend that holds + * the key (memory before keychain), or null. + */ + async locate(key: string): Promise { + const all = await this.locateAll(key); + return all[0] ?? null; + } +} + +export function isSecretStoreRegistry(store: SecretStore): store is SecretStoreRegistry { + return store instanceof SecretStoreRegistry; +} + +/** + * Memory is only meaningful with {@link SecretStoreRegistry}. Reject it when the + * daemon is running a plain SecretStore so clients cannot request memory while + * the write silently lands in keychain (or another non-ephemeral backend). + */ +export function requireNamespacedMemory( + registry: SecretStoreRegistry | null, + backend: string, +): { ok: true } | { ok: false; error: string } { + if (backend === 'memory' && !registry) { + return { + ok: false, + error: + 'secret_store=memory requires a namespaced secret store (memory + keychain); ' + + 'this daemon is not running with SecretStoreRegistry', + }; + } + return { ok: true }; +} + +/** + * Parse SetSecret / ConfigureProvider `store` / `secret_store` field. + * Unspecified / omitted / keychain → keychain (default for writes). + * ENV rejected for writes unless `allowEnv` (configure/reference only). + */ +export function parseSecretStoreChoice( + store: unknown, + opts: { allowEnv: true }, +): { ok: true; backend: SecretStoreKind } | { ok: false; error: string }; +export function parseSecretStoreChoice( + store: unknown, + opts?: { allowEnv?: false | undefined }, +): { ok: true; backend: SecretBackend } | { ok: false; error: string }; +export function parseSecretStoreChoice( + store: unknown, + opts?: { allowEnv?: boolean }, +): { ok: true; backend: SecretStoreKind } | { ok: false; error: string } { + if ( + store === undefined || + store === null || + store === '' || + store === 0 || + store === '0' || + store === 'SECRET_STORE_UNSPECIFIED' || + store === 'keychain' || + store === 'SECRET_STORE_KEYCHAIN' || + store === 1 || + store === '1' + ) { + return { ok: true, backend: 'keychain' }; + } + if (store === 'memory' || store === 'SECRET_STORE_MEMORY' || store === 3 || store === '3') { + return { ok: true, backend: 'memory' }; + } + if (store === 'env' || store === 'SECRET_STORE_ENV' || store === 2 || store === '2') { + if (opts?.allowEnv) { + return { ok: true, backend: 'env' }; + } + return { + ok: false, + error: + 'SECRET_STORE_ENV is not writable via the secrets API; configure the provider with ' + + 'secret_name + secret_store=env (or api_key_env_var_name)', + }; + } + return { ok: false, error: `Unsupported secret store: ${String(store)}` }; +} + +/** 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 + return 0; // SECRET_STORE_UNSPECIFIED +} diff --git a/packages/daemon/src/daemon/server/abbenay-service.test.ts b/packages/daemon/src/daemon/server/abbenay-service.test.ts index cdb47455..a936b67c 100644 --- a/packages/daemon/src/daemon/server/abbenay-service.test.ts +++ b/packages/daemon/src/daemon/server/abbenay-service.test.ts @@ -88,9 +88,13 @@ vi.mock('../../core/session-summarizer.js', () => ({ generateSessionSummary: (...a: unknown[]) => mockGenerateSessionSummary(...a), })); -vi.mock('../../core/secrets.js', () => ({ - auditSecretChange: (...a: unknown[]) => mockAuditSecretChange(...a), -})); +vi.mock('../../core/secrets.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + auditSecretChange: (...a: unknown[]) => mockAuditSecretChange(...a), + }; +}); vi.mock('../../core/provider-endpoint.js', () => ({ auditProviderEndpointChange: (...a: unknown[]) => mockAuditProviderEndpointChange(...a), @@ -117,7 +121,7 @@ import { authorizeInlinePolicy, DEFAULT_CONSUMER_AUTH_CONTEXT, } from './abbenay-service.js'; -import { withEnv } from './test-env.js'; +import { withEnv } from '../../../tests/helpers/with-env.js'; describe('configFileToProto', () => { it('converts an empty config', () => { @@ -398,6 +402,19 @@ describe('protoToConfigFile', () => { expect(config.providers!['my-provider'].api_key_keychain_name).toBeUndefined(); }); + it('maps proto secret_store enum values onto provider config', () => { + const config = protoToConfigFile({ + providers: { + 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' }, + }, + }); + expect(config.providers!.mem.secret_store).toBe('memory'); + expect(config.providers!.envp.secret_store).toBe('env'); + expect(config.providers!.kc.secret_store).toBe('keychain'); + }); + it('provider with no models gets undefined models field', () => { const proto = { providers: { @@ -978,6 +995,135 @@ describe('createAbbenayService handlers', () => { expect(mockAuditSecretChange).toHaveBeenCalled(); }); + it('ConfigureProvider writes api_key to memory and rejects conflicting credential fields', async () => { + const { SecretStoreRegistry } = await import('../secrets/registry.js'); + const { MemorySecretStore } = await import('../../core/secrets.js'); + const registry = new SecretStoreRegistry(new MemorySecretStore(), new MemorySecretStore()); + const state = createMockState({ secretStore: registry }); + const service = createServiceHandlers(state); + + const mem = await invokeUnary(service.ConfigureProvider, { + provider_id: 'mem-mock', + engine: 'mock', + api_key: 'ephemeral', + secret_store: 'SECRET_STORE_MEMORY', + secret_name: 'MEM_MOCK_API_KEY', + }); + expect(mem.response?.success).toBe(true); + expect(await registry.getFrom('memory', 'MEM_MOCK_API_KEY')).toBe('ephemeral'); + expect(await registry.hasIn('keychain', 'MEM_MOCK_API_KEY')).toBe(false); + expect(mockAuditSecretChange).toHaveBeenCalledWith({ + key: 'MEM_MOCK_API_KEY', + op: 'set', + source: 'grpc-configure-memory', + }); + + expect((await invokeUnary(service.ConfigureProvider, { + provider_id: 'bad-combo', + engine: 'mock', + api_key: 'x', + env_var_name: 'Y', + })).error?.code).toBe(grpc.status.INVALID_ARGUMENT); + + expect((await invokeUnary(service.ConfigureProvider, { + provider_id: 'bad-combo-2', + engine: 'mock', + secret_name: 'X', + env_var_name: 'Y', + })).error?.code).toBe(grpc.status.INVALID_ARGUMENT); + + expect((await invokeUnary(service.ConfigureProvider, { + provider_id: 'bad-store', + engine: 'mock', + api_key: 'x', + secret_store: 'SECRET_STORE_ENV', + })).error?.code).toBe(grpc.status.INVALID_ARGUMENT); + + expect((await invokeUnary(service.ConfigureProvider, { + provider_id: 'missing-secret', + engine: 'mock', + secret_name: 'DOES_NOT_EXIST', + secret_store: 'SECRET_STORE_MEMORY', + })).error?.code).toBe(grpc.status.INVALID_ARGUMENT); + + // secret_name without store defaults to keychain lookup. + await registry.setIn('keychain', 'DEFAULT_KC', 'v'); + const def = await invokeUnary(service.ConfigureProvider, { + provider_id: 'default-kc', + engine: 'mock', + secret_name: 'DEFAULT_KC', + }); + expect(def.response?.success).toBe(true); + + expect((await invokeUnary(service.ConfigureProvider, { + provider_id: 'bad-ref-store', + engine: 'mock', + secret_name: 'X', + secret_store: 'vault', + })).error?.code).toBe(grpc.status.INVALID_ARGUMENT); + }); + + it('Secrets RPCs reject unsupported store selections', async () => { + const state = createMockState(); + const service = createServiceHandlers(state); + expect((await invokeUnary(service.GetSecret, { key: 'K', store: 'vault' })).error?.code) + .toBe(grpc.status.INVALID_ARGUMENT); + expect((await invokeUnary(service.DeleteSecret, { key: 'K', store: 'SECRET_STORE_ENV' })).error?.code) + .toBe(grpc.status.INVALID_ARGUMENT); + }); + + it('Secrets RPCs reject memory when secret store is not a registry', async () => { + const set = vi.fn().mockResolvedValue(undefined); + const state = createMockState({ + secretStore: { + get: vi.fn().mockResolvedValue(null), + set, + delete: vi.fn().mockResolvedValue(true), + has: vi.fn().mockResolvedValue(false), + }, + }); + const service = createServiceHandlers(state); + + for (const method of ['GetSecret', 'SetSecret', 'DeleteSecret'] as const) { + const req = + method === 'SetSecret' + ? { key: 'K', value: 'v', store: 'SECRET_STORE_MEMORY' } + : { key: 'K', store: 'SECRET_STORE_MEMORY' }; + const { error } = await invokeUnary(service[method], req); + expect(error?.code, method).toBe(grpc.status.INVALID_ARGUMENT); + expect(error?.message, method).toMatch(/memory|SecretStoreRegistry/i); + } + expect(set).not.toHaveBeenCalled(); + + const configure = await invokeUnary(service.ConfigureProvider, { + provider_id: 'plain-mem', + engine: 'mock', + api_key: 'x', + secret_store: 'SECRET_STORE_MEMORY', + }); + expect(configure.error?.code).toBe(grpc.status.INVALID_ARGUMENT); + expect(set).not.toHaveBeenCalled(); + }); + + it('ListSecrets returns empty has_value rows when registry backends lack the key', async () => { + mockGetEngine.mockImplementation((id: string) => ( + id === 'mock' ? { id: 'mock', requiresKey: false, defaultEnvVar: 'MOCK_API_KEY' } + : id === 'secure' ? { id: 'secure', requiresKey: true, defaultEnvVar: 'SECURE_API_KEY' } + : undefined + )); + // getEngines is mocked at module level to return mock+secure + const { SecretStoreRegistry } = await import('../secrets/registry.js'); + const { MemorySecretStore } = await import('../../core/secrets.js'); + const registry = new SecretStoreRegistry(new MemorySecretStore(), new MemorySecretStore()); + const state = createMockState({ secretStore: registry }); + const service = createServiceHandlers(state); + const listed = await invokeUnary(service.ListSecrets, {}); + const rows = rpcArray<{ key: string; has_value?: boolean }>(listed.response, 'secrets'); + const secure = rows.filter((r) => r.key === 'SECURE_API_KEY'); + expect(secure.length).toBeGreaterThanOrEqual(1); + expect(secure.every((r) => r.has_value === false)).toBe(true); + }); + it('RemoveProvider deletes provider and keychain secret', async () => { mockLoadConfig.mockReturnValue({ providers: { @@ -995,6 +1141,75 @@ describe('createAbbenayService handlers', () => { expect(state.notifyModelsChanged).toHaveBeenCalledWith('provider_removed'); }); + it('RemoveProvider deletes owned memory secret from the memory backend only', async () => { + const { SecretStoreRegistry } = await import('../secrets/registry.js'); + const { MemorySecretStore } = await import('../../core/secrets.js'); + const memory = new MemorySecretStore(); + const keychain = new MemorySecretStore(); + await memory.set('MYMOCK_API_KEY', 'mem-value'); + await keychain.set('MYMOCK_API_KEY', 'kc-value'); + const registry = new SecretStoreRegistry(memory, keychain); + + mockLoadConfig.mockReturnValue({ + providers: { + mymock: { + engine: 'mock', + secret_name: 'MYMOCK_API_KEY', + secret_store: 'memory', + }, + }, + }); + const state = createMockState({ secretStore: registry }); + const service = createServiceHandlers(state); + + const { error } = await invokeUnary(service.RemoveProvider, { provider_id: 'mymock' }); + expect(error).toBeNull(); + expect(await memory.has('MYMOCK_API_KEY')).toBe(false); + expect(await keychain.has('MYMOCK_API_KEY')).toBe(true); + expect(mockAuditSecretChange).toHaveBeenCalledWith({ + key: 'MYMOCK_API_KEY', + op: 'delete', + source: 'grpc-configure', + }); + }); + + it('RemoveProvider skips env-backed and shared secret names', async () => { + const { SecretStoreRegistry } = await import('../secrets/registry.js'); + const { MemorySecretStore } = await import('../../core/secrets.js'); + const memory = new MemorySecretStore(); + const keychain = new MemorySecretStore(); + await keychain.set('SHARED_OPENAI', 'shared'); + await keychain.set('ENV_LOOKALIKE', 'should-remain'); + const registry = new SecretStoreRegistry(memory, keychain); + const state = createMockState({ secretStore: registry }); + const service = createServiceHandlers(state); + + mockLoadConfig.mockReturnValue({ + providers: { + 'env-openai': { + engine: 'mock', + secret_name: 'ENV_LOOKALIKE', + secret_store: 'env', + }, + }, + }); + expect((await invokeUnary(service.RemoveProvider, { provider_id: 'env-openai' })).error).toBeNull(); + expect(await keychain.has('ENV_LOOKALIKE')).toBe(true); + + mockLoadConfig.mockReturnValue({ + providers: { + 'shared-openai': { + engine: 'mock', + secret_name: 'SHARED_OPENAI', + secret_store: 'keychain', + }, + }, + }); + expect((await invokeUnary(service.RemoveProvider, { provider_id: 'shared-openai' })).error).toBeNull(); + expect(await keychain.has('SHARED_OPENAI')).toBe(true); + expect(mockAuditSecretChange).not.toHaveBeenCalled(); + }); + it('GetKeyStatus checks keychain and env sources', async () => { const state = createMockState({ secretStore: { has: vi.fn().mockResolvedValue(true) }, @@ -1018,6 +1233,22 @@ describe('createAbbenayService handlers', () => { expect(missing.error?.code).toBe(grpc.status.INVALID_ARGUMENT); }); + it('GetKeyStatus checks memory 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(); + await memory.set('MEM_ONLY', 'v'); + const registry = new SecretStoreRegistry(memory, keychain); + const state = createMockState({ secretStore: registry }); + const service = createServiceHandlers(state); + + const mem = await invokeUnary(service.GetKeyStatus, { source: 'memory', name: 'MEM_ONLY' }); + expect(mem.response?.exists).toBe(true); + const kc = await invokeUnary(service.GetKeyStatus, { source: 'keychain', name: 'MEM_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 0afa6cc9..b94a0d84 100644 --- a/packages/daemon/src/daemon/server/abbenay-service.ts +++ b/packages/daemon/src/daemon/server/abbenay-service.ts @@ -22,9 +22,16 @@ import { maybeSummarize, generateSessionSummary } from '../../core/session-summa import { loadConfig, saveConfig, loadWorkspaceConfig, saveWorkspaceConfig, getUserConfigPath, getWorkspaceConfigPath, isValidVirtualName, + providerSecretName, isProviderOwnedSecretName, type ConfigFile, type ProviderConfig as DaemonProviderConfig, type McpServerConfig, } from '../../core/config.js'; import { auditSecretChange } from '../../core/secrets.js'; +import { + isSecretStoreRegistry, + parseSecretStoreChoice, + requireNamespacedMemory, + secretBackendToProto, +} from '../secrets/registry.js'; import { auditProviderEndpointChange, auditProviderEndpointConfigDiff, @@ -149,15 +156,19 @@ interface ChatRequestProto { interface GetSecretRequestProto { key: string; + store?: number | string; } interface SetSecretRequestProto { key: string; value: string; + /** Proto SecretStore enum: number or string name depending on codec. */ + store?: number | string; } interface DeleteSecretRequestProto { key: string; + store?: number | string; } interface GetProviderStatusRequestProto { @@ -255,6 +266,8 @@ interface FullProviderConfigProto { api_key_env_var_name?: string; base_url?: string; models?: Record; + secret_name?: string; + secret_store?: number | string; } interface ModelParamConfigProto { @@ -315,6 +328,13 @@ interface ConfigureProviderRequestProto { target?: string; workspace_path?: string; workspacePath?: string; + secret_name?: string; + secretName?: string; + /** @deprecated Alias for secret_name (PR transition). */ + api_key_keychain_name?: string; + apiKeyKeychainName?: string; + secret_store?: number | string; + secretStore?: number | string; } interface RemoveProviderRequestProto { @@ -785,8 +805,29 @@ export function createAbbenayService( ): void { if (!requireCapability(call, 'secrets', authContext, callback)) return; const key = call.request.key; - - state.secretStore.get(key).then((value) => { + const parsed = parseSecretStoreChoice(call.request.store); + if (!parsed.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: parsed.error, + }); + return; + } + + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, parsed.backend); + if (!memoryOk.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: memoryOk.error, + }); + return; + } + const read = registry + ? registry.getFrom(parsed.backend, key) + : state.secretStore.get(key); + + read.then((value) => { callback(null, { value: value || '', found: value !== null, @@ -807,10 +848,34 @@ export function createAbbenayService( callback: grpc.sendUnaryData ): void { if (!requireCapability(call, 'secrets', authContext, callback)) return; - const { key, value } = call.request; - - state.secretStore.set(key, value).then(() => { - auditSecretChange({ key, op: 'set', source: 'grpc-secrets' }); + const { key, value, store } = call.request; + + const parsed = parseSecretStoreChoice(store); + if (!parsed.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: parsed.error, + }); + return; + } + + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, parsed.backend); + if (!memoryOk.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: memoryOk.error, + }); + return; + } + const write = registry + ? registry.setIn(parsed.backend, key, value) + : state.secretStore.set(key, value); + const auditSource = + parsed.backend === 'memory' ? 'grpc-secrets-memory' : 'grpc-secrets'; + + write.then(() => { + auditSecretChange({ key, op: 'set', source: auditSource }); callback(null, {}); // Notify VS Code that models may have changed (new API key) state.notifyModelsChanged('secret_updated'); @@ -830,8 +895,36 @@ export function createAbbenayService( callback: grpc.sendUnaryData ): void { if (!requireCapability(call, 'secrets', authContext, callback)) return; - state.secretStore.delete(call.request.key).then(() => { - auditSecretChange({ key: call.request.key, op: 'delete', source: 'grpc-secrets' }); + const key = call.request.key; + const parsed = parseSecretStoreChoice(call.request.store); + if (!parsed.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: parsed.error, + }); + return; + } + + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, parsed.backend); + if (!memoryOk.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: memoryOk.error, + }); + return; + } + const remove = registry + ? registry.deleteFrom(parsed.backend, key) + : state.secretStore.delete(key); + + remove.then(() => { + auditSecretChange({ + key, + op: 'delete', + source: + parsed.backend === 'memory' ? 'grpc-secrets-memory' : 'grpc-secrets', + }); callback(null, {}); }).catch((error: unknown) => { callback({ @@ -849,16 +942,39 @@ export function createAbbenayService( callback: grpc.sendUnaryData ): void { if (!requireCapability(call, 'secrets', authContext, callback)) return; - // Return known key names with availability status + // Return known key names with availability status (one row per backend hit). const engines = getEngines(); + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; const checks = engines.filter(e => e.requiresKey).map(async (e) => { const key = e.defaultEnvVar || `${e.id.toUpperCase()}_API_KEY`; + if (registry) { + const backends = await registry.locateAll(key); + if (backends.length === 0) { + return [{ + key, + engine: e.id, + has_value: false, + store: secretBackendToProto(null), + }]; + } + return backends.map((backend) => ({ + key, + engine: e.id, + has_value: true, + store: secretBackendToProto(backend), + })); + } const hasValue = await state.secretStore.has(key); - return { key, engine: e.id, has_value: hasValue }; + return [{ + key, + engine: e.id, + has_value: hasValue, + store: secretBackendToProto(null), + }]; }); - Promise.all(checks).then((secrets) => { - callback(null, { secrets }); + Promise.all(checks).then((groups) => { + callback(null, { secrets: groups.flat() }); }).catch((error: unknown) => { callback({ code: grpc.status.INTERNAL, @@ -1678,6 +1794,12 @@ export function createAbbenayService( const engine = call.request.engine; const apiKey = call.request.api_key || call.request.apiKey; const envVarName = call.request.env_var_name || call.request.envVarName; + const secretNameRef = + call.request.secret_name || + call.request.secretName || + call.request.api_key_keychain_name || + call.request.apiKeyKeychainName; + const secretStoreRaw = call.request.secret_store ?? call.request.secretStore; const baseUrl = call.request.base_url || call.request.baseUrl; const target = call.request.target || 'user'; const workspacePath = call.request.workspace_path || call.request.workspacePath; @@ -1728,13 +1850,129 @@ export function createAbbenayService( config.providers[providerId] = existing as DaemonProviderConfig; + if (apiKey && envVarName) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: 'Provide only one of api_key or env_var_name', + }); + return; + } + if (secretNameRef && envVarName) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: 'Provide only one of secret_name or env_var_name', + }); + return; + } + if (apiKey) { - const keychainName = `${providerId.toUpperCase()}_API_KEY`; - await state.secretStore.set(keychainName, apiKey); - auditSecretChange({ key: keychainName, op: 'set', source: 'grpc-configure' }); - config.providers[providerId].api_key_keychain_name = keychainName; + const storeChoice = parseSecretStoreChoice(secretStoreRaw); + if (!storeChoice.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: storeChoice.error, + }); + return; + } + // Explicit name, or legacy invent from provider id (1:1 shortcut). + const secretName = secretNameRef || `${providerId.toUpperCase()}_API_KEY`; + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, storeChoice.backend); + if (!memoryOk.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: memoryOk.error, + }); + return; + } + if (registry) { + await registry.setIn(storeChoice.backend, secretName, apiKey); + } else { + await state.secretStore.set(secretName, apiKey); + } + const auditSource = + storeChoice.backend === 'memory' ? 'grpc-configure-memory' : 'grpc-configure'; + auditSecretChange({ key: secretName, op: 'set', source: auditSource }); + config.providers[providerId].secret_name = secretName; + config.providers[providerId].secret_store = storeChoice.backend; + config.providers[providerId].api_key_keychain_name = secretName; delete config.providers[providerId].api_key_env_var_name; + } else if (secretNameRef) { + const storeRaw = secretStoreRaw; + const wantsEnv = + storeRaw === 'env' || + storeRaw === 'SECRET_STORE_ENV' || + storeRaw === 2 || + storeRaw === '2'; + + if ( + wantsEnv || + (storeRaw !== undefined && storeRaw !== null && storeRaw !== '') + ) { + const storeChoice = parseSecretStoreChoice(storeRaw, { allowEnv: true }); + if (!storeChoice.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: storeChoice.error, + }); + return; + } + if (storeChoice.backend === 'env') { + config.providers[providerId].secret_name = secretNameRef; + config.providers[providerId].secret_store = 'env'; + config.providers[providerId].api_key_env_var_name = secretNameRef; + delete config.providers[providerId].api_key_keychain_name; + } else { + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, storeChoice.backend); + if (!memoryOk.ok) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: memoryOk.error, + }); + return; + } + const exists = registry + ? await registry.hasIn(storeChoice.backend, secretNameRef) + : await state.secretStore.has(secretNameRef); + if (!exists) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: + `Secret "${secretNameRef}" not found in ${storeChoice.backend}; ` + + `SetSecret first or pass api_key`, + }); + return; + } + config.providers[providerId].secret_name = secretNameRef; + config.providers[providerId].secret_store = storeChoice.backend; + config.providers[providerId].api_key_keychain_name = secretNameRef; + delete config.providers[providerId].api_key_env_var_name; + } + } else { + // No secret_store: default namespace is keychain. + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const exists = registry + ? await registry.hasIn('keychain', secretNameRef) + : await state.secretStore.has(secretNameRef); + if (!exists) { + callback({ + code: grpc.status.INVALID_ARGUMENT, + message: + `Secret "${secretNameRef}" not found in keychain; ` + + `SetSecret first, pass api_key, or set secret_store`, + }); + return; + } + config.providers[providerId].secret_name = secretNameRef; + config.providers[providerId].secret_store = 'keychain'; + config.providers[providerId].api_key_keychain_name = secretNameRef; + delete config.providers[providerId].api_key_env_var_name; + } } else if (envVarName) { + // Legacy shortcut → secret_name + secret_store=env (var need not exist yet). + config.providers[providerId].secret_name = envVarName; + config.providers[providerId].secret_store = 'env'; config.providers[providerId].api_key_env_var_name = envVarName; delete config.providers[providerId].api_key_keychain_name; } @@ -1795,11 +2033,26 @@ export function createAbbenayService( : loadConfig()) || { providers: {} }; if (config.providers && config.providers[providerId]) { - const keychainName = config.providers[providerId].api_key_keychain_name; - if (keychainName) { + const providerCfg = config.providers[providerId]; + const secretName = providerSecretName(providerCfg); + // Env-backed names are not store keys; shared picks must outlive the provider. + if ( + secretName && + providerCfg.secret_store !== 'env' && + isProviderOwnedSecretName(providerId, secretName) + ) { try { - await state.secretStore.delete(keychainName); - auditSecretChange({ key: keychainName, op: 'delete', source: 'grpc-configure' }); + const registry = isSecretStoreRegistry(state.secretStore) + ? state.secretStore + : null; + const backend = providerCfg.secret_store === 'memory' ? 'memory' : 'keychain'; + if (registry) { + await registry.deleteFrom(backend, secretName); + } else if (backend === 'keychain') { + // Memory without a registry was never a valid write target. + await state.secretStore.delete(secretName); + } + auditSecretChange({ key: secretName, op: 'delete', source: 'grpc-configure' }); } catch { /* ignore */ } } delete config.providers[providerId]; @@ -1855,8 +2108,15 @@ export function createAbbenayService( (async () => { try { let exists = false; - if (source === 'keychain') { - exists = await state.secretStore.has(name); + if (source === 'keychain' || source === 'memory') { + const registry = isSecretStoreRegistry(state.secretStore) + ? state.secretStore + : null; + exists = registry + ? await registry.hasIn(source, name) + : source === 'keychain' + ? await state.secretStore.has(name) + : false; } else if (source === 'env') { exists = !!process.env[name]; } @@ -2105,10 +2365,23 @@ export function configFileToProto(config: ConfigFile): ConfigProto { }; } } + const secretName = providerSecretName(pcfg); providers[pid] = { engine: pcfg.engine || pid, - api_key_keychain_name: pcfg.api_key_keychain_name, - api_key_env_var_name: pcfg.api_key_env_var_name, + secret_name: secretName, + secret_store: + pcfg.secret_store === 'env' + ? 2 + : pcfg.secret_store === 'memory' + ? 3 + : secretName + ? 1 + : 0, + api_key_keychain_name: pcfg.secret_store === 'env' ? undefined : secretName, + api_key_env_var_name: + pcfg.secret_store === 'env' + ? secretName || pcfg.api_key_env_var_name + : pcfg.api_key_env_var_name, base_url: pcfg.base_url, models, }; @@ -2184,10 +2457,27 @@ export function protoToConfigFile(proto: ConfigProto): ConfigFile { }; } } + const secretName = pcfg.secret_name || pcfg.api_key_keychain_name || undefined; + let secretStore: 'memory' | 'keychain' | 'env' | 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 === 1 || pcfg.secret_store === 'SECRET_STORE_KEYCHAIN' || pcfg.secret_store === 'keychain') { + secretStore = 'keychain'; + } else if (pcfg.api_key_env_var_name && !secretName) { + secretStore = 'env'; + } + const envName = + secretStore === 'env' + ? (secretName || pcfg.api_key_env_var_name || undefined) + : (pcfg.api_key_env_var_name || undefined); config.providers[pid] = { engine: pcfg.engine || pid, - api_key_keychain_name: pcfg.api_key_keychain_name || undefined, - api_key_env_var_name: pcfg.api_key_env_var_name || undefined, + secret_name: secretStore === 'env' ? envName : secretName, + secret_store: secretStore, + api_key_keychain_name: secretStore === 'env' ? undefined : secretName, + api_key_env_var_name: envName, base_url: pcfg.base_url || undefined, models: Object.keys(models).length > 0 ? models : undefined, }; diff --git a/packages/daemon/src/daemon/server/consumer-auth.test.ts b/packages/daemon/src/daemon/server/consumer-auth.test.ts index 2b56d9fd..05f25978 100644 --- a/packages/daemon/src/daemon/server/consumer-auth.test.ts +++ b/packages/daemon/src/daemon/server/consumer-auth.test.ts @@ -18,7 +18,7 @@ import { type ConsumerCapability, } from './consumer-auth.js'; import type { ConfigFile } from '../../core/config.js'; -import { withEnv } from './test-env.js'; +import { withEnv } from '../../../tests/helpers/with-env.js'; function mockGrpcCall(token?: string): { metadata: grpc.Metadata } { const metadata = new grpc.Metadata(); diff --git a/packages/daemon/src/daemon/state.ts b/packages/daemon/src/daemon/state.ts index b369bd18..10ff623a 100644 --- a/packages/daemon/src/daemon/state.ts +++ b/packages/daemon/src/daemon/state.ts @@ -9,6 +9,8 @@ import { v4 as uuidv4 } from 'uuid'; import { CoreState, type ChatToolOptions } from '../core/state.js'; import type { ChatChunk, ChatParams, ToolExecutor } from '../core/engines.js'; 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 { ToolRegistry } from '../core/tool-registry.js'; import { ToolRouter } from './tool-router.js'; @@ -82,7 +84,9 @@ export class DaemonState extends CoreState { public readonly sessionStore: SessionStore; constructor() { - super({ secretStore: new KeychainSecretStore() }); + super({ + secretStore: new SecretStoreRegistry(new MemorySecretStore(), new KeychainSecretStore()), + }); // Initialize tool registry, router, MCP client pool, and MCP server this.toolRegistry = new ToolRegistry(); diff --git a/packages/daemon/src/daemon/web/api-schemas.test.ts b/packages/daemon/src/daemon/web/api-schemas.test.ts index efda9a20..f3d943e7 100644 --- a/packages/daemon/src/daemon/web/api-schemas.test.ts +++ b/packages/daemon/src/daemon/web/api-schemas.test.ts @@ -64,6 +64,55 @@ describe('PostProviderConfigureBodySchema', () => { }); expect(result.success).toBe(true); }); + + it('accepts secretName to pick an existing secret', () => { + expect( + PostProviderConfigureBodySchema.safeParse({ + engine: 'openai', + secretName: 'SHARED_OPENAI', + }).success, + ).toBe(true); + }); + + it('accepts secretStore memory|keychain|env with apiKey or secretName', () => { + expect( + PostProviderConfigureBodySchema.safeParse({ + engine: 'openai', + apiKey: 'sk-test', + secretStore: 'memory', + secretName: 'MY_KEY', + }).success, + ).toBe(true); + expect( + PostProviderConfigureBodySchema.safeParse({ + engine: 'openai', + secretName: 'ENV_VAR', + secretStore: 'env', + }).success, + ).toBe(true); + }); + + it('rejects apiKey together with envVarName', () => { + const result = PostProviderConfigureBodySchema.safeParse({ + engine: 'openai', + apiKey: 'sk-test', + envVarName: 'OPENAI_API_KEY', + }); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues.some((i) => i.path.includes('apiKey'))).toBe(true); + } + }); + + it('rejects secretName together with envVarName', () => { + expect( + PostProviderConfigureBodySchema.safeParse({ + engine: 'openai', + secretName: 'SHARED', + envVarName: 'OPENAI_API_KEY', + }).success, + ).toBe(false); + }); }); describe('PostPolicyBodySchema', () => { @@ -124,6 +173,21 @@ describe('secret body schemas', () => { expect(PostSecretByKeyBodySchema.safeParse({ value: 'secret' }).success).toBe(true); expect(PostSecretByKeyBodySchema.safeParse({ value: '' }).success).toBe(false); }); + + it('accepts optional secret_store memory|keychain|env (and legacy store)', () => { + expect( + PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secret_store: 'memory' }).success, + ).toBe(true); + expect( + PostSecretByKeyBodySchema.safeParse({ value: 'v', store: 'keychain' }).success, + ).toBe(true); + expect( + PostSecretBodySchema.safeParse({ key: 'K', value: 'v', secret_store: 'env' }).success, + ).toBe(true); + expect( + PostSecretBodySchema.safeParse({ key: 'K', value: 'v', store: 'vault' }).success, + ).toBe(false); + }); }); describe('MCP decision schemas', () => { diff --git a/packages/daemon/src/daemon/web/api-schemas.ts b/packages/daemon/src/daemon/web/api-schemas.ts index 40a78993..4f073dc8 100644 --- a/packages/daemon/src/daemon/web/api-schemas.ts +++ b/packages/daemon/src/daemon/web/api-schemas.ts @@ -42,9 +42,15 @@ export const PostConfigBodySchema = z // ── Secrets ───────────────────────────────────────────────────────────── +const SecretStoreFieldSchema = z.enum(['memory', 'keychain', 'env']).optional(); + export const PostSecretByKeyBodySchema = z .object({ value: z.string().min(1), + /** Default: keychain (persistent). memory = process-lifetime only. */ + secret_store: SecretStoreFieldSchema, + /** @deprecated Use secret_store. */ + store: SecretStoreFieldSchema, }) .strict(); @@ -52,6 +58,10 @@ export const PostSecretBodySchema = z .object({ key: z.string().min(1), value: z.string().min(1), + /** Default: keychain (persistent). memory = process-lifetime only. */ + secret_store: SecretStoreFieldSchema, + /** @deprecated Use secret_store. */ + store: SecretStoreFieldSchema, }) .strict(); @@ -102,6 +112,12 @@ export const PostProviderConfigureBodySchema = z .object({ engine: z.string().min(1).optional(), 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(), + /** @deprecated Use secretName. */ + apiKeyKeychainName: z.string().min(1).optional(), envVarName: z.string().min(1).optional(), /** Format-checked here; host policy applied in the route with server config. */ baseUrl: ProviderBaseUrlSchema.optional(), @@ -117,6 +133,21 @@ export const PostProviderConfigureBodySchema = z path: ['workspacePath'], }); } + if (data.apiKey && data.envVarName) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Provide only one of apiKey or envVarName', + path: ['apiKey'], + }); + } + const secretName = data.secretName || data.apiKeyKeychainName; + if (secretName && data.envVarName) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Provide only one of secretName or envVarName', + path: ['secretName'], + }); + } }); // ── Policies ──────────────────────────────────────────────────────────── diff --git a/packages/daemon/src/daemon/web/server.ts b/packages/daemon/src/daemon/web/server.ts index 5e5edeed..2e978b0c 100644 --- a/packages/daemon/src/daemon/web/server.ts +++ b/packages/daemon/src/daemon/web/server.ts @@ -16,8 +16,9 @@ import * as path from 'node:path'; import * as fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { getDefaultSocketPath } from '../transport.js'; -import { loadConfig, saveConfig, loadWorkspaceConfig, saveWorkspaceConfig, getUserConfigPath, getWorkspaceConfigPath, type ConfigFile, type ProviderConfig } from '../../core/config.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 { auditProviderEndpointChange, auditProviderEndpointConfigDiff, @@ -1023,14 +1024,27 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): app.get('/api/secrets', async (req, res) => { try { const engines = getEngines(); - const secrets = await Promise.all( + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const groups = await Promise.all( engines.filter(e => e.requiresKey).map(async (e) => { const key = e.defaultEnvVar || `${e.id.toUpperCase()}_API_KEY`; + if (registry) { + const backends = await registry.locateAll(key); + if (backends.length === 0) { + return [{ key, engine: e.id, hasValue: false }]; + } + return backends.map((store) => ({ + key, + engine: e.id, + hasValue: true, + store, + })); + } const hasValue = await state.secretStore.has(key); - return { key, engine: e.id, hasValue }; + return [{ key, engine: e.id, hasValue }]; }) ); - res.json({ secrets }); + res.json({ secrets: groups.flat() }); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); console.error('[Web] /api/secrets error:', msg); @@ -1040,7 +1054,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): /** * POST /api/secrets/:key - Set a specific secret (API key) - * Body: { value: string } + * Body: { value: string, store?: "memory" | "keychain" } */ app.post('/api/secrets/:key', async (req, res) => { try { @@ -1050,9 +1064,28 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): sendBadRequest(res, parsed.error); return; } - await state.secretStore.set(key, parsed.data.value); - auditSecretChange({ key, op: 'set', source: 'http-secrets' }); - res.json({ success: true }); + const storeChoice = parseSecretStoreChoice( + parsed.data.secret_store ?? parsed.data.store ?? 'keychain', + ); + if (!storeChoice.ok) { + sendBadRequest(res, storeChoice.error); + return; + } + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, storeChoice.backend); + if (!memoryOk.ok) { + sendBadRequest(res, memoryOk.error); + return; + } + if (registry) { + await registry.setIn(storeChoice.backend, key, parsed.data.value); + } else { + await state.secretStore.set(key, parsed.data.value); + } + const auditSource = + storeChoice.backend === 'memory' ? 'http-secrets-memory' : 'http-secrets'; + auditSecretChange({ key, op: 'set', source: auditSource }); + res.json({ success: true, secret_store: storeChoice.backend }); state.notifyModelsChanged('secret_updated'); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); @@ -1060,10 +1093,10 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): res.status(500).json({ error: msg }); } }); - + /** * POST /api/secrets - Set a secret (API key) — legacy route - * Body: { key: string, value: string } + * Body: { key: string, value: string, secret_store?: "memory" | "keychain" } */ app.post('/api/secrets', async (req, res) => { try { @@ -1072,9 +1105,28 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): sendBadRequest(res, parsed.error); return; } - await state.secretStore.set(parsed.data.key, parsed.data.value); - auditSecretChange({ key: parsed.data.key, op: 'set', source: 'http-secrets' }); - res.json({ success: true }); + const storeChoice = parseSecretStoreChoice( + parsed.data.secret_store ?? parsed.data.store ?? 'keychain', + ); + if (!storeChoice.ok) { + sendBadRequest(res, storeChoice.error); + return; + } + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, storeChoice.backend); + if (!memoryOk.ok) { + sendBadRequest(res, memoryOk.error); + return; + } + if (registry) { + await registry.setIn(storeChoice.backend, parsed.data.key, parsed.data.value); + } else { + await state.secretStore.set(parsed.data.key, parsed.data.value); + } + const auditSource = + storeChoice.backend === 'memory' ? 'http-secrets-memory' : 'http-secrets'; + auditSecretChange({ key: parsed.data.key, op: 'set', source: auditSource }); + res.json({ success: true, secret_store: storeChoice.backend }); state.notifyModelsChanged('secret_updated'); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); @@ -1085,6 +1137,7 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): /** * DELETE /api/secrets/:key - Delete a secret + * Query: secret_store=memory|keychain (default keychain) */ app.delete('/api/secrets/:key', async (req, res) => { try { @@ -1093,9 +1146,33 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): sendBadRequest(res, parsed.error); return; } - await state.secretStore.delete(req.params.key); - auditSecretChange({ key: req.params.key, op: 'delete', source: 'http-secrets' }); - res.json({ success: true }); + const storeChoice = parseSecretStoreChoice( + (req.query.secret_store as string | undefined) ?? + (req.query.store as string | undefined) ?? + 'keychain', + ); + if (!storeChoice.ok) { + sendBadRequest(res, storeChoice.error); + return; + } + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, storeChoice.backend); + if (!memoryOk.ok) { + sendBadRequest(res, memoryOk.error); + return; + } + if (registry) { + await registry.deleteFrom(storeChoice.backend, req.params.key); + } else { + await state.secretStore.delete(req.params.key); + } + auditSecretChange({ + key: req.params.key, + op: 'delete', + source: + storeChoice.backend === 'memory' ? 'http-secrets-memory' : 'http-secrets', + }); + res.json({ success: true, secret_store: storeChoice.backend }); state.notifyModelsChanged('secret_deleted'); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); @@ -1119,8 +1196,13 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): } let exists = false; - if (source === 'keychain') { - exists = await state.secretStore.has(name); + if (source === 'keychain' || source === 'memory') { + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + exists = registry + ? await registry.hasIn(source, name) + : source === 'keychain' + ? await state.secretStore.has(name) + : false; } else if (source === 'env') { exists = !!process.env[name]; } @@ -1328,7 +1410,18 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): sendBadRequest(res, parsed.error); return; } - const { engine, apiKey, envVarName, baseUrl, target, workspacePath } = parsed.data; + const { + engine, + apiKey, + secretName: secretNameBody, + secretStore: secretStoreBody, + apiKeyKeychainName, + envVarName, + baseUrl, + target, + workspacePath, + } = parsed.data; + const secretNameRef = secretNameBody || apiKeyKeychainName; let resolvedWorkspace: string | undefined; if (target === 'workspace' && workspacePath) { @@ -1379,12 +1472,81 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): config.providers[providerId] = existing as ProviderConfig; if (apiKey) { - const keychainName = `${providerId.toUpperCase()}_API_KEY`; - await state.secretStore.set(keychainName, apiKey); - auditSecretChange({ key: keychainName, op: 'set', source: 'http-configure' }); - config.providers[providerId].api_key_keychain_name = keychainName; + const storeChoice = parseSecretStoreChoice(secretStoreBody ?? 'keychain'); + if (!storeChoice.ok) { + sendBadRequest(res, storeChoice.error); + return; + } + // Explicit name, or legacy invent from provider id (1:1 shortcut). + const secretName = secretNameRef || `${providerId.toUpperCase()}_API_KEY`; + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, storeChoice.backend); + if (!memoryOk.ok) { + sendBadRequest(res, memoryOk.error); + return; + } + if (registry) { + await registry.setIn(storeChoice.backend, secretName, apiKey); + } else { + await state.secretStore.set(secretName, apiKey); + } + const auditSource = + storeChoice.backend === 'memory' ? 'http-configure-memory' : 'http-configure'; + auditSecretChange({ key: secretName, op: 'set', source: auditSource }); + config.providers[providerId].secret_name = secretName; + config.providers[providerId].secret_store = storeChoice.backend; + config.providers[providerId].api_key_keychain_name = secretName; delete config.providers[providerId].api_key_env_var_name; + } else if (secretNameRef) { + const storeRaw = secretStoreBody; + if (storeRaw === 'env') { + config.providers[providerId].secret_name = secretNameRef; + 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') { + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const memoryOk = requireNamespacedMemory(registry, storeRaw); + if (!memoryOk.ok) { + sendBadRequest(res, memoryOk.error); + return; + } + const exists = registry + ? await registry.hasIn(storeRaw, secretNameRef) + : await state.secretStore.has(secretNameRef); + if (!exists) { + res.status(400).json({ + error: + `Secret "${secretNameRef}" not found in ${storeRaw}; ` + + `POST /api/secrets first or pass apiKey`, + }); + return; + } + config.providers[providerId].secret_name = secretNameRef; + config.providers[providerId].secret_store = storeRaw; + config.providers[providerId].api_key_keychain_name = secretNameRef; + delete config.providers[providerId].api_key_env_var_name; + } else { + const registry = isSecretStoreRegistry(state.secretStore) ? state.secretStore : null; + const exists = registry + ? await registry.hasIn('keychain', secretNameRef) + : await state.secretStore.has(secretNameRef); + if (!exists) { + res.status(400).json({ + error: + `Secret "${secretNameRef}" not found in keychain; ` + + `POST /api/secrets first, pass apiKey, or set secret_store`, + }); + return; + } + config.providers[providerId].secret_name = secretNameRef; + config.providers[providerId].secret_store = 'keychain'; + config.providers[providerId].api_key_keychain_name = secretNameRef; + delete config.providers[providerId].api_key_env_var_name; + } } else if (envVarName) { + config.providers[providerId].secret_name = envVarName; + config.providers[providerId].secret_store = 'env'; config.providers[providerId].api_key_env_var_name = envVarName; delete config.providers[providerId].api_key_keychain_name; } @@ -1451,11 +1613,26 @@ export function createWebApp(state: DaemonState, options?: WebSecurityOptions): : loadConfig()) || { providers: {} }; if (config.providers && config.providers[providerId]) { - const keychainName = config.providers[providerId].api_key_keychain_name; - if (keychainName) { + const providerCfg = config.providers[providerId]; + const secretName = providerSecretName(providerCfg); + // Env-backed names are not store keys; shared picks must outlive the provider. + if ( + secretName && + providerCfg.secret_store !== 'env' && + isProviderOwnedSecretName(providerId, secretName) + ) { try { - await state.secretStore.delete(keychainName); - auditSecretChange({ key: keychainName, op: 'delete', source: 'http-configure' }); + const registry = isSecretStoreRegistry(state.secretStore) + ? state.secretStore + : null; + const backend = providerCfg.secret_store === 'memory' ? 'memory' : 'keychain'; + if (registry) { + await registry.deleteFrom(backend, secretName); + } else if (backend === 'keychain') { + // Memory without a registry was never a valid write target. + await state.secretStore.delete(secretName); + } + auditSecretChange({ key: secretName, op: 'delete', source: 'http-configure' }); } catch { /* ignore */ } } diff --git a/packages/daemon/tests/helpers/with-env.test.ts b/packages/daemon/tests/helpers/with-env.test.ts new file mode 100644 index 00000000..56b68034 --- /dev/null +++ b/packages/daemon/tests/helpers/with-env.test.ts @@ -0,0 +1,30 @@ +/** + * Unit tests for the withEnv test helper. + */ + +import { describe, it, expect } from 'vitest'; +import { withEnv } from './with-env.js'; + +describe('withEnv', () => { + it('restores a previously set env value after the callback', async () => { + const key = 'ABBENAY_TEST_ENV_RESTORE'; + process.env[key] = 'original'; + try { + await withEnv(key, 'temporary', () => { + expect(process.env[key]).toBe('temporary'); + }); + expect(process.env[key]).toBe('original'); + } finally { + delete process.env[key]; + } + }); + + it('deletes the key when it was previously unset', async () => { + const key = 'ABBENAY_TEST_ENV_DELETE'; + delete process.env[key]; + await withEnv(key, 'temporary', () => { + expect(process.env[key]).toBe('temporary'); + }); + expect(process.env[key]).toBeUndefined(); + }); +}); diff --git a/packages/daemon/src/daemon/server/test-env.ts b/packages/daemon/tests/helpers/with-env.ts similarity index 73% rename from packages/daemon/src/daemon/server/test-env.ts rename to packages/daemon/tests/helpers/with-env.ts index d6da8bdd..47d30d9e 100644 --- a/packages/daemon/src/daemon/server/test-env.ts +++ b/packages/daemon/tests/helpers/with-env.ts @@ -9,7 +9,10 @@ export async function withEnv( try { await fn(); } finally { - if (prev === undefined) delete process.env[key]; - else process.env[key] = prev; + if (prev === undefined) { + delete process.env[key]; + } else { + process.env[key] = prev; + } } } diff --git a/packages/daemon/tests/integration/consumer-auth.test.ts b/packages/daemon/tests/integration/consumer-auth.test.ts index a96b2d1d..f5fbbbdc 100644 --- a/packages/daemon/tests/integration/consumer-auth.test.ts +++ b/packages/daemon/tests/integration/consumer-auth.test.ts @@ -43,6 +43,8 @@ vi.mock('../../src/core/config.js', () => ({ getUserConfigPath: () => '/tmp/abbenay-test-config.yaml', getWorkspaceConfigPath: () => '/tmp/abbenay-test-ws-config.yaml', isValidVirtualName: (n: string) => /^[a-z0-9][a-z0-9._-]*$/.test(n), + providerSecretName: (cfg: { secret_name?: string; api_key_keychain_name?: string }) => + cfg.secret_name || cfg.api_key_keychain_name, })); vi.mock('../../src/core/engines.js', () => ({ @@ -270,6 +272,109 @@ describe('Consumer auth RPC gating', () => { expect(res.success).toBe(true); }); + it('ConfigureProvider references an existing named secret (N:1)', async () => { + await callUnary(client, 'SetSecret', { + key: 'SHARED_OPENAI', + value: 'sk-shared', + store: 'SECRET_STORE_MEMORY', + }, GOOD_TOKEN); + + const res = await callUnary(client, 'ConfigureProvider', { + provider_id: 'work-openai', + engine: 'mock', + secret_name: 'SHARED_OPENAI', + secret_store: 'SECRET_STORE_MEMORY', + }, GOOD_TOKEN); + expect(res.success).toBe(true); + + const saved = mockSaveConfig.mock.calls.at(-1)?.[0] as { + providers: { + 'work-openai': { + secret_name?: string; + secret_store?: string; + api_key_keychain_name?: string; + }; + }; + }; + expect(saved.providers['work-openai'].secret_name).toBe('SHARED_OPENAI'); + expect(saved.providers['work-openai'].secret_store).toBe('memory'); + expect(saved.providers['work-openai'].api_key_keychain_name).toBe('SHARED_OPENAI'); + // Reference path must not invent a provider-scoped key name or rewrite the secret. + expect(mockSecretStoreData.has('WORK-OPENAI_API_KEY')).toBe(false); + }); + + it('ConfigureProvider rejects missing secret_name', async () => { + await expect( + callUnary(client, 'ConfigureProvider', { + provider_id: 'missing-key-provider', + engine: 'mock', + secret_name: 'DOES_NOT_EXIST', + }, GOOD_TOKEN), + ).rejects.toMatchObject({ code: grpc.status.INVALID_ARGUMENT }); + }); + + it('ConfigureProvider references env via secret_store=env', async () => { + process.env.ABBENAY_TEST_PROVIDER_ENV = 'sk-from-env'; + try { + const res = await callUnary(client, 'ConfigureProvider', { + provider_id: 'env-openai', + engine: 'mock', + secret_name: 'ABBENAY_TEST_PROVIDER_ENV', + secret_store: 'SECRET_STORE_ENV', + }, GOOD_TOKEN); + expect(res.success).toBe(true); + + const saved = mockSaveConfig.mock.calls.at(-1)?.[0] as { + providers: { + 'env-openai': { + secret_name?: string; + secret_store?: string; + api_key_env_var_name?: string; + }; + }; + }; + expect(saved.providers['env-openai'].secret_name).toBe('ABBENAY_TEST_PROVIDER_ENV'); + expect(saved.providers['env-openai'].secret_store).toBe('env'); + expect(saved.providers['env-openai'].api_key_env_var_name).toBe('ABBENAY_TEST_PROVIDER_ENV'); + } finally { + delete process.env.ABBENAY_TEST_PROVIDER_ENV; + } + }); + + it('ConfigureProvider accepts secret_store=env even when the env var is unset', async () => { + delete process.env.ABBENAY_TEST_PROVIDER_ENV_MISSING; + const res = await callUnary(client, 'ConfigureProvider', { + provider_id: 'env-missing', + engine: 'mock', + secret_name: 'ABBENAY_TEST_PROVIDER_ENV_MISSING', + secret_store: 'SECRET_STORE_ENV', + }, GOOD_TOKEN); + expect(res.success).toBe(true); + const saved = mockSaveConfig.mock.calls.at(-1)?.[0] as { + providers: { 'env-missing': { secret_store?: string } }; + }; + expect(saved.providers['env-missing'].secret_store).toBe('env'); + }); + + it('ConfigureProvider stores api_key under an explicit secret_name', async () => { + const res = await callUnary(client, 'ConfigureProvider', { + provider_id: 'named-write', + engine: 'mock', + api_key: 'sk-explicit', + secret_name: 'MY_EXPLICIT_KEY', + secret_store: 'SECRET_STORE_KEYCHAIN', + }, GOOD_TOKEN); + expect(res.success).toBe(true); + expect(mockSecretStoreData.get('MY_EXPLICIT_KEY')).toBe('sk-explicit'); + + const saved = mockSaveConfig.mock.calls.at(-1)?.[0] as { + providers: { + 'named-write': { secret_name?: string; api_key_keychain_name?: string }; + }; + }; + expect(saved.providers['named-write'].secret_name).toBe('MY_EXPLICIT_KEY'); + }); + it('denies Shutdown without shutdown capability', async () => { await expectPermissionDenied(callUnary(client, 'Shutdown', {}, 'chat-only')); }); diff --git a/packages/daemon/tests/integration/grpc-real-service.test.ts b/packages/daemon/tests/integration/grpc-real-service.test.ts index 871608c8..8b51970c 100644 --- a/packages/daemon/tests/integration/grpc-real-service.test.ts +++ b/packages/daemon/tests/integration/grpc-real-service.test.ts @@ -282,6 +282,124 @@ describe('Real gRPC service: Secrets', () => { expect(res.secrets).toBeDefined(); expect(Array.isArray(res.secrets)).toBe(true); }); + + it('should store SECRET_STORE_MEMORY without writing keychain backend', async () => { + await callUnary(client, 'SetSecret', { + key: 'MEM_ONLY', + value: 'ephemeral-value', + store: 'SECRET_STORE_MEMORY', + }); + const got = await callUnary(client, 'GetSecret', { + key: 'MEM_ONLY', + store: 'SECRET_STORE_MEMORY', + }); + expect(got.value).toBe('ephemeral-value'); + // Default GetSecret (keychain namespace) must not see the memory value. + const fromDefault = await callUnary(client, 'GetSecret', { key: 'MEM_ONLY' }); + expect(fromDefault.value).toBe(''); + // DaemonState uses SecretStoreRegistry; mocked KeychainSecretStore is the keychain half. + // Memory-only writes must not land in the keychain Map. + expect(mockSecretStoreData.has('MEM_ONLY')).toBe(false); + }); + + it('should allow the same key name in memory and keychain (discrete namespaces)', async () => { + await callUnary(client, 'SetSecret', { + key: 'BOTH_KEY', + value: 'persistent', + store: 'SECRET_STORE_KEYCHAIN', + }); + expect(mockSecretStoreData.get('BOTH_KEY')).toBe('persistent'); + + await callUnary(client, 'SetSecret', { + key: 'BOTH_KEY', + value: 'ephemeral', + store: 'SECRET_STORE_MEMORY', + }); + expect(mockSecretStoreData.get('BOTH_KEY')).toBe('persistent'); + const mem = await callUnary(client, 'GetSecret', { + key: 'BOTH_KEY', + store: 'SECRET_STORE_MEMORY', + }); + expect(mem.value).toBe('ephemeral'); + const kc = await callUnary(client, 'GetSecret', { + key: 'BOTH_KEY', + store: 'SECRET_STORE_KEYCHAIN', + }); + expect(kc.value).toBe('persistent'); + }); + + it('should reject SECRET_STORE_ENV on SetSecret', async () => { + await expect( + callUnary(client, 'SetSecret', { + key: 'ENV_KEY', + value: 'nope', + store: 'SECRET_STORE_ENV', + }), + ).rejects.toMatchObject({ code: grpc.status.INVALID_ARGUMENT }); + }); + + it('should delete SECRET_STORE_MEMORY without touching keychain', async () => { + await callUnary(client, 'SetSecret', { + key: 'BOTH_DEL', + value: 'persistent', + store: 'SECRET_STORE_KEYCHAIN', + }); + await callUnary(client, 'SetSecret', { + key: 'BOTH_DEL', + value: 'ephemeral', + store: 'SECRET_STORE_MEMORY', + }); + + await callUnary(client, 'DeleteSecret', { + key: 'BOTH_DEL', + store: 'SECRET_STORE_MEMORY', + }); + + const mem = await callUnary(client, 'GetSecret', { + key: 'BOTH_DEL', + store: 'SECRET_STORE_MEMORY', + }); + expect(mem.value).toBe(''); + const kc = await callUnary(client, 'GetSecret', { + key: 'BOTH_DEL', + store: 'SECRET_STORE_KEYCHAIN', + }); + expect(kc.value).toBe('persistent'); + expect(mockSecretStoreData.get('BOTH_DEL')).toBe('persistent'); + }); + + it('ListSecrets emits one row per backend that holds an engine key', async () => { + mockGetEngines.mockReturnValueOnce([ + { + id: 'openai', + requiresKey: true, + defaultEnvVar: 'OPENAI_API_KEY', + supportsTools: true, + createModel: () => { throw new Error('mock'); }, + }, + ]); + + await callUnary(client, 'SetSecret', { + key: 'OPENAI_API_KEY', + value: 'kc', + store: 'SECRET_STORE_KEYCHAIN', + }); + await callUnary(client, 'SetSecret', { + key: 'OPENAI_API_KEY', + value: 'mem', + store: 'SECRET_STORE_MEMORY', + }); + + const res = await callUnary(client, 'ListSecrets', {}); + const openaiRows = (res.secrets as Array<{ key: string; store?: string | number }>).filter( + (s) => s.key === 'OPENAI_API_KEY' && s.store, + ); + expect(openaiRows.length).toBe(2); + const stores = openaiRows.map((r) => String(r.store)); + expect(stores).toEqual( + expect.arrayContaining(['SECRET_STORE_MEMORY', 'SECRET_STORE_KEYCHAIN']), + ); + }); }); describe('Real gRPC service: Chat streaming', () => { diff --git a/packages/daemon/tests/integration/web-sse.test.ts b/packages/daemon/tests/integration/web-sse.test.ts index 91e40f69..4c3e6275 100644 --- a/packages/daemon/tests/integration/web-sse.test.ts +++ b/packages/daemon/tests/integration/web-sse.test.ts @@ -37,7 +37,8 @@ import { createWebApp } from '../../src/daemon/web/server.js'; import type { DaemonState } from '../../src/daemon/state.js'; import type { ProviderInfo, ModelInfo } from '../../src/core/state.js'; import type { ConnectedClient } from '../../src/daemon/state.js'; -import type { SecretStore } from '../../src/core/secrets.js'; +import { MemorySecretStore } from '../../src/core/secrets.js'; +import { SecretStoreRegistry } from '../../src/daemon/secrets/registry.js'; // ─── Mock DaemonState ─────────────────────────────────────────────────── @@ -58,13 +59,11 @@ let mockChatConfig: MockChatConfig = { const chatRequests: Array<{ model: string; messages: any[] }> = []; -/** In-memory secret store for testing */ -const mockSecretStore: SecretStore = { - async get(key: string) { return key === 'OPENAI_API_KEY' ? 'sk-test' : null; }, - async set(_key: string, _value: string) {}, - async delete(_key: string) { return true; }, - async has(key: string) { return key === 'OPENAI_API_KEY'; }, -}; +/** Namespaced secret store matching daemon production wiring */ +const mockSecretStore = new SecretStoreRegistry( + new MemorySecretStore(), + new MemorySecretStore(), +); function sleep(ms: number): Promise { return new Promise((r) => setTimeout(r, ms)); @@ -141,6 +140,7 @@ let httpServer: http.Server; let baseUrl: string; beforeAll(async () => { + await mockSecretStore.setIn('keychain', 'OPENAI_API_KEY', 'sk-test'); const state = createMockState(); const app = createWebApp(state, { apiToken: 'test-integration-token', skipConfig: true }); @@ -476,6 +476,229 @@ describe('Web API - Secrets Endpoints', () => { expect(body.success).toBe(true); }); + it('POST /api/secrets/:key should accept secret_store=memory', async () => { + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/secrets/MEM_KEY`, { + value: 'ephemeral', + secret_store: 'memory', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + expect(body.secret_store).toBe('memory'); + }); + + it('POST /api/secrets should reject secret_store=env', async () => { + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/secrets`, { + key: 'ENV_KEY', + value: 'nope', + secret_store: 'env', + }); + expect(statusCode).toBe(400); + expect(body.error).toMatch(/ENV/i); + }); + + it('DELETE /api/secrets/:key?secret_store=memory clears memory only', async () => { + await httpRequest('POST', `${baseUrl}/api/secrets/BOTH_HTTP`, { + value: 'persistent', + secret_store: 'keychain', + }); + await httpRequest('POST', `${baseUrl}/api/secrets/BOTH_HTTP`, { + value: 'ephemeral', + secret_store: 'memory', + }); + + const { statusCode, body } = await httpRequest( + 'DELETE', + `${baseUrl}/api/secrets/BOTH_HTTP?secret_store=memory`, + ); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + expect(body.secret_store).toBe('memory'); + + const memStatus = await httpRequest( + 'GET', + `${baseUrl}/api/key-status?source=memory&name=BOTH_HTTP`, + ); + expect(memStatus.statusCode).toBe(200); + expect(memStatus.body.exists).toBe(false); + + const kcStatus = await httpRequest( + 'GET', + `${baseUrl}/api/key-status?source=keychain&name=BOTH_HTTP`, + ); + expect(kcStatus.statusCode).toBe(200); + expect(kcStatus.body.exists).toBe(true); + }); + + it('GET /api/key-status?source=memory reports memory-only secrets', async () => { + await httpRequest('POST', `${baseUrl}/api/secrets/MEM_STATUS`, { + value: 'ephemeral', + secret_store: 'memory', + }); + const mem = await httpRequest( + 'GET', + `${baseUrl}/api/key-status?source=memory&name=MEM_STATUS`, + ); + expect(mem.statusCode).toBe(200); + expect(mem.body.exists).toBe(true); + const kc = await httpRequest( + 'GET', + `${baseUrl}/api/key-status?source=keychain&name=MEM_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', + apiKey: 'sk-mem', + secretName: 'MEM_OPENAI_API_KEY', + secretStore: 'memory', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + expect(await mockSecretStore.getFrom('memory', 'MEM_OPENAI_API_KEY')).toBe('sk-mem'); + expect(await mockSecretStore.hasIn('keychain', 'MEM_OPENAI_API_KEY')).toBe(false); + }); + + it('POST /api/provider/:id/configure picks an existing memory secret', async () => { + await httpRequest('POST', `${baseUrl}/api/secrets/SHARED_MEM`, { + value: 'shared-value', + secret_store: 'memory', + }); + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/shared-openai/configure`, { + engine: 'openai', + secretName: 'SHARED_MEM', + secretStore: 'memory', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + }); + + 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`, { + engine: 'openai', + secretName: 'ABBENAY_HTTP_ENV', + secretStore: 'env', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + expect(await mockSecretStore.locateAll('ABBENAY_HTTP_ENV')).toEqual(before); + }); + + it('POST /api/provider/:id/configure rejects missing referenced secret', async () => { + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/missing-openai/configure`, { + engine: 'openai', + secretName: 'DOES_NOT_EXIST', + secretStore: 'memory', + }); + expect(statusCode).toBe(400); + expect(body.error).toMatch(/not found/i); + }); + + it('POST /api/provider/:id/configure defaults omitted secretStore to keychain', async () => { + await httpRequest('POST', `${baseUrl}/api/secrets/DEFAULT_KC`, { + value: 'kc-value', + secret_store: 'keychain', + }); + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/default-kc/configure`, { + engine: 'openai', + secretName: 'DEFAULT_KC', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + }); + + it('POST /api/provider/:id/configure rejects missing keychain secret when store omitted', async () => { + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/missing-kc/configure`, { + engine: 'openai', + secretName: 'NO_SUCH_KEYCHAIN_SECRET', + }); + expect(statusCode).toBe(400); + expect(body.error).toMatch(/not found in keychain/i); + }); + + it('POST /api/provider/:id/configure accepts envVarName legacy field', async () => { + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/legacy-env/configure`, { + engine: 'openai', + envVarName: 'ABBENAY_LEGACY_ENV', + }); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + }); + + it('POST /api/secrets/:key rejects invalid store and legacy POST accepts memory', async () => { + const bad = await httpRequest('POST', `${baseUrl}/api/secrets/BAD_STORE`, { + value: 'x', + secret_store: 'vault', + }); + expect(bad.statusCode).toBe(400); + + const legacy = await httpRequest('POST', `${baseUrl}/api/secrets`, { + key: 'LEGACY_MEM', + value: 'v', + store: 'memory', + }); + expect(legacy.statusCode).toBe(200); + expect(legacy.body.secret_store).toBe('memory'); + expect(await mockSecretStore.getFrom('memory', 'LEGACY_MEM')).toBe('v'); + }); + + it('DELETE /api/secrets/:key rejects invalid store', async () => { + const { statusCode } = await httpRequest( + 'DELETE', + `${baseUrl}/api/secrets/ANY?secret_store=vault`, + ); + expect(statusCode).toBe(400); + }); + + it('POST /api/provider/:id/configure rejects apiKey with secretStore=env', async () => { + const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/provider/bad-openai/configure`, { + engine: 'openai', + apiKey: 'sk-x', + secretStore: 'env', + }); + expect(statusCode).toBe(400); + expect(body.error).toMatch(/ENV|not writable|secrets API/i); + }); + + it('DELETE /api/provider/:id removes owned memory secret only', async () => { + await httpRequest('POST', `${baseUrl}/api/provider/owndel/configure`, { + engine: 'openai', + apiKey: 'sk-owned', + secretName: 'OWNDEL_API_KEY', + secretStore: 'memory', + }); + await mockSecretStore.setIn('keychain', 'OWNDEL_API_KEY', 'keep-me'); + + const { statusCode, body } = await httpRequest('DELETE', `${baseUrl}/api/provider/owndel`); + expect(statusCode).toBe(200); + expect(body.success).toBe(true); + expect(await mockSecretStore.hasIn('memory', 'OWNDEL_API_KEY')).toBe(false); + expect(await mockSecretStore.hasIn('keychain', 'OWNDEL_API_KEY')).toBe(true); + }); + + it('DELETE /api/provider/:id skips shared and env-backed secrets', async () => { + await mockSecretStore.setIn('keychain', 'SHARED_KEEP', 'shared'); + // Seed config with shared + env providers via configure endpoints. + await httpRequest('POST', `${baseUrl}/api/provider/shared-keep/configure`, { + engine: 'openai', + secretName: 'SHARED_KEEP', + secretStore: 'keychain', + }); + await httpRequest('POST', `${baseUrl}/api/provider/env-keep/configure`, { + engine: 'openai', + secretName: 'ENV_KEEP', + secretStore: 'env', + }); + + expect((await httpRequest('DELETE', `${baseUrl}/api/provider/shared-keep`)).statusCode).toBe(200); + expect(await mockSecretStore.hasIn('keychain', 'SHARED_KEEP')).toBe(true); + + expect((await httpRequest('DELETE', `${baseUrl}/api/provider/env-keep`)).statusCode).toBe(200); + }); + it('POST /api/secrets/:key should reject missing value', async () => { const { statusCode, body } = await httpRequest('POST', `${baseUrl}/api/secrets/TEST_KEY`, {}); expect(statusCode).toBe(400); diff --git a/packages/proto-ts/src/abbenay/v1/service.ts b/packages/proto-ts/src/abbenay/v1/service.ts index 44944e10..a69bcb11 100644 --- a/packages/proto-ts/src/abbenay/v1/service.ts +++ b/packages/proto-ts/src/abbenay/v1/service.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.11.5 -// protoc v3.19.6 +// protoc v6.33.4 // source: abbenay/v1/service.proto /* eslint-disable */ @@ -1089,10 +1089,20 @@ export interface Config_ConsumersEntry { export interface FullProviderConfig { engine: string; - apiKeyKeychainName?: string | undefined; + /** deprecated: use secret_name */ + apiKeyKeychainName?: + | string + | undefined; + /** deprecated: use secret_name + secret_store=env */ apiKeyEnvVarName?: string | undefined; baseUrl?: string | undefined; models: { [key: string]: ModelParamConfig }; + /** Logical name (store key or env var) */ + secretName?: + | string + | undefined; + /** MEMORY | KEYCHAIN | ENV */ + secretStore?: SecretStore | undefined; } export interface FullProviderConfig_ModelsEntry { @@ -1189,7 +1199,27 @@ export interface ConsumerCapabilitiesMsg { | boolean | undefined; /** Allow dynamic MCP server registration */ - mcpRegister?: boolean | undefined; + mcpRegister?: + | boolean + | undefined; + /** Allow secret read/write/list RPCs */ + secrets?: + | boolean + | undefined; + /** Allow GetConfig / UpdateConfig / policy / web control */ + config?: + | boolean + | undefined; + /** Allow ConfigureProvider / RemoveProvider / DiscoverModels */ + providers?: + | boolean + | undefined; + /** Allow Shutdown RPC */ + shutdown?: + | boolean + | undefined; + /** Allow Chat / SessionChat */ + chat?: boolean | undefined; } export interface UpdateConfigRequest { @@ -1207,7 +1237,7 @@ export interface ConfigureProviderRequest { engine?: | string | undefined; - /** Raw API key (daemon stores in keychain) */ + /** Raw API key value (optional with secret_name) */ apiKey?: | string | undefined; @@ -1224,7 +1254,22 @@ export interface ConfigureProviderRequest { | string | undefined; /** Required if target is "workspace" */ - workspacePath?: string | undefined; + workspacePath?: + | string + | undefined; + /** + * Logical secret name (SetSecret key). Prefer this over inventing a name from + * provider_id. With api_key: store the value under this name. Without api_key: + * reference an existing secret (N providers may share one key). + */ + secretName?: + | string + | undefined; + /** + * Where to write api_key when provided (MEMORY or KEYCHAIN). Default KEYCHAIN. + * Ignored when only referencing an existing secret_name. + */ + secretStore?: SecretStore | undefined; } export interface ConfigureProviderResponse { @@ -1298,6 +1343,8 @@ export interface DeletePolicyRequest { export interface GetSecretRequest { key: string; + /** Backend namespace (default keychain) */ + store?: SecretStore | undefined; } export interface GetSecretResponse { @@ -1307,12 +1354,14 @@ export interface GetSecretResponse { export interface SetSecretRequest { key: string; value: string; - /** Where to store */ - store: SecretStore; + /** Where to store (default keychain) */ + store?: SecretStore | undefined; } export interface DeleteSecretRequest { key: string; + /** Backend namespace (default keychain) */ + store?: SecretStore | undefined; } export interface ListSecretsRequest { @@ -10868,7 +10917,15 @@ export const Config_ConsumersEntry: MessageFns = { }; function createBaseFullProviderConfig(): FullProviderConfig { - return { engine: "", apiKeyKeychainName: undefined, apiKeyEnvVarName: undefined, baseUrl: undefined, models: {} }; + return { + engine: "", + apiKeyKeychainName: undefined, + apiKeyEnvVarName: undefined, + baseUrl: undefined, + models: {}, + secretName: undefined, + secretStore: undefined, + }; } export const FullProviderConfig: MessageFns = { @@ -10888,6 +10945,12 @@ export const FullProviderConfig: MessageFns = { globalThis.Object.entries(message.models).forEach(([key, value]: [string, ModelParamConfig]) => { FullProviderConfig_ModelsEntry.encode({ key: key as any, value }, writer.uint32(42).fork()).join(); }); + if (message.secretName !== undefined) { + writer.uint32(50).string(message.secretName); + } + if (message.secretStore !== undefined) { + writer.uint32(56).int32(message.secretStore); + } return writer; }, @@ -10941,6 +11004,22 @@ export const FullProviderConfig: MessageFns = { } continue; } + case 6: { + if (tag !== 50) { + break; + } + + message.secretName = reader.string(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.secretStore = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -10977,6 +11056,16 @@ export const FullProviderConfig: MessageFns = { {}, ) : {}, + secretName: isSet(object.secretName) + ? globalThis.String(object.secretName) + : isSet(object.secret_name) + ? globalThis.String(object.secret_name) + : undefined, + secretStore: isSet(object.secretStore) + ? secretStoreFromJSON(object.secretStore) + : isSet(object.secret_store) + ? secretStoreFromJSON(object.secret_store) + : undefined, }; }, @@ -11003,6 +11092,12 @@ export const FullProviderConfig: MessageFns = { }); } } + if (message.secretName !== undefined) { + obj.secretName = message.secretName; + } + if (message.secretStore !== undefined) { + obj.secretStore = secretStoreToJSON(message.secretStore); + } return obj; }, @@ -11024,6 +11119,8 @@ export const FullProviderConfig: MessageFns = { }, {}, ); + message.secretName = object.secretName ?? undefined; + message.secretStore = object.secretStore ?? undefined; return message; }, }; @@ -12059,7 +12156,15 @@ export const ConsumerConfigMsg: MessageFns = { }; function createBaseConsumerCapabilitiesMsg(): ConsumerCapabilitiesMsg { - return { inlinePolicy: undefined, mcpRegister: undefined }; + return { + inlinePolicy: undefined, + mcpRegister: undefined, + secrets: undefined, + config: undefined, + providers: undefined, + shutdown: undefined, + chat: undefined, + }; } export const ConsumerCapabilitiesMsg: MessageFns = { @@ -12070,6 +12175,21 @@ export const ConsumerCapabilitiesMsg: MessageFns = { if (message.mcpRegister !== undefined) { writer.uint32(16).bool(message.mcpRegister); } + if (message.secrets !== undefined) { + writer.uint32(24).bool(message.secrets); + } + if (message.config !== undefined) { + writer.uint32(32).bool(message.config); + } + if (message.providers !== undefined) { + writer.uint32(40).bool(message.providers); + } + if (message.shutdown !== undefined) { + writer.uint32(48).bool(message.shutdown); + } + if (message.chat !== undefined) { + writer.uint32(56).bool(message.chat); + } return writer; }, @@ -12096,6 +12216,46 @@ export const ConsumerCapabilitiesMsg: MessageFns = { message.mcpRegister = reader.bool(); continue; } + case 3: { + if (tag !== 24) { + break; + } + + message.secrets = reader.bool(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.config = reader.bool(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.providers = reader.bool(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.shutdown = reader.bool(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.chat = reader.bool(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -12117,6 +12277,11 @@ export const ConsumerCapabilitiesMsg: MessageFns = { : isSet(object.mcp_register) ? globalThis.Boolean(object.mcp_register) : undefined, + secrets: isSet(object.secrets) ? globalThis.Boolean(object.secrets) : undefined, + config: isSet(object.config) ? globalThis.Boolean(object.config) : undefined, + providers: isSet(object.providers) ? globalThis.Boolean(object.providers) : undefined, + shutdown: isSet(object.shutdown) ? globalThis.Boolean(object.shutdown) : undefined, + chat: isSet(object.chat) ? globalThis.Boolean(object.chat) : undefined, }; }, @@ -12128,6 +12293,21 @@ export const ConsumerCapabilitiesMsg: MessageFns = { if (message.mcpRegister !== undefined) { obj.mcpRegister = message.mcpRegister; } + if (message.secrets !== undefined) { + obj.secrets = message.secrets; + } + if (message.config !== undefined) { + obj.config = message.config; + } + if (message.providers !== undefined) { + obj.providers = message.providers; + } + if (message.shutdown !== undefined) { + obj.shutdown = message.shutdown; + } + if (message.chat !== undefined) { + obj.chat = message.chat; + } return obj; }, @@ -12138,6 +12318,11 @@ export const ConsumerCapabilitiesMsg: MessageFns = { const message = createBaseConsumerCapabilitiesMsg(); message.inlinePolicy = object.inlinePolicy ?? undefined; message.mcpRegister = object.mcpRegister ?? undefined; + message.secrets = object.secrets ?? undefined; + message.config = object.config ?? undefined; + message.providers = object.providers ?? undefined; + message.shutdown = object.shutdown ?? undefined; + message.chat = object.chat ?? undefined; return message; }, }; @@ -12229,6 +12414,8 @@ function createBaseConfigureProviderRequest(): ConfigureProviderRequest { baseUrl: undefined, target: undefined, workspacePath: undefined, + secretName: undefined, + secretStore: undefined, }; } @@ -12255,6 +12442,12 @@ export const ConfigureProviderRequest: MessageFns = { if (message.workspacePath !== undefined) { writer.uint32(58).string(message.workspacePath); } + if (message.secretName !== undefined) { + writer.uint32(66).string(message.secretName); + } + if (message.secretStore !== undefined) { + writer.uint32(72).int32(message.secretStore); + } return writer; }, @@ -12321,6 +12514,22 @@ export const ConfigureProviderRequest: MessageFns = { message.workspacePath = reader.string(); continue; } + case 8: { + if (tag !== 66) { + break; + } + + message.secretName = reader.string(); + continue; + } + case 9: { + if (tag !== 72) { + break; + } + + message.secretStore = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -12359,6 +12568,16 @@ export const ConfigureProviderRequest: MessageFns = { : isSet(object.workspace_path) ? globalThis.String(object.workspace_path) : undefined, + secretName: isSet(object.secretName) + ? globalThis.String(object.secretName) + : isSet(object.secret_name) + ? globalThis.String(object.secret_name) + : undefined, + secretStore: isSet(object.secretStore) + ? secretStoreFromJSON(object.secretStore) + : isSet(object.secret_store) + ? secretStoreFromJSON(object.secret_store) + : undefined, }; }, @@ -12385,6 +12604,12 @@ export const ConfigureProviderRequest: MessageFns = { if (message.workspacePath !== undefined) { obj.workspacePath = message.workspacePath; } + if (message.secretName !== undefined) { + obj.secretName = message.secretName; + } + if (message.secretStore !== undefined) { + obj.secretStore = secretStoreToJSON(message.secretStore); + } return obj; }, @@ -12400,6 +12625,8 @@ export const ConfigureProviderRequest: MessageFns = { message.baseUrl = object.baseUrl ?? undefined; message.target = object.target ?? undefined; message.workspacePath = object.workspacePath ?? undefined; + message.secretName = object.secretName ?? undefined; + message.secretStore = object.secretStore ?? undefined; return message; }, }; @@ -13373,7 +13600,7 @@ export const DeletePolicyRequest: MessageFns = { }; function createBaseGetSecretRequest(): GetSecretRequest { - return { key: "" }; + return { key: "", store: undefined }; } export const GetSecretRequest: MessageFns = { @@ -13381,6 +13608,9 @@ export const GetSecretRequest: MessageFns = { if (message.key !== "") { writer.uint32(10).string(message.key); } + if (message.store !== undefined) { + writer.uint32(16).int32(message.store); + } return writer; }, @@ -13399,6 +13629,14 @@ export const GetSecretRequest: MessageFns = { message.key = reader.string(); continue; } + case 2: { + if (tag !== 16) { + break; + } + + message.store = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -13409,7 +13647,10 @@ export const GetSecretRequest: MessageFns = { }, fromJSON(object: any): GetSecretRequest { - return { key: isSet(object.key) ? globalThis.String(object.key) : "" }; + return { + key: isSet(object.key) ? globalThis.String(object.key) : "", + store: isSet(object.store) ? secretStoreFromJSON(object.store) : undefined, + }; }, toJSON(message: GetSecretRequest): unknown { @@ -13417,6 +13658,9 @@ export const GetSecretRequest: MessageFns = { if (message.key !== "") { obj.key = message.key; } + if (message.store !== undefined) { + obj.store = secretStoreToJSON(message.store); + } return obj; }, @@ -13426,6 +13670,7 @@ export const GetSecretRequest: MessageFns = { fromPartial(object: DeepPartial): GetSecretRequest { const message = createBaseGetSecretRequest(); message.key = object.key ?? ""; + message.store = object.store ?? undefined; return message; }, }; @@ -13489,7 +13734,7 @@ export const GetSecretResponse: MessageFns = { }; function createBaseSetSecretRequest(): SetSecretRequest { - return { key: "", value: "", store: 0 }; + return { key: "", value: "", store: undefined }; } export const SetSecretRequest: MessageFns = { @@ -13500,7 +13745,7 @@ export const SetSecretRequest: MessageFns = { if (message.value !== "") { writer.uint32(18).string(message.value); } - if (message.store !== 0) { + if (message.store !== undefined) { writer.uint32(24).int32(message.store); } return writer; @@ -13550,7 +13795,7 @@ export const SetSecretRequest: MessageFns = { return { key: isSet(object.key) ? globalThis.String(object.key) : "", value: isSet(object.value) ? globalThis.String(object.value) : "", - store: isSet(object.store) ? secretStoreFromJSON(object.store) : 0, + store: isSet(object.store) ? secretStoreFromJSON(object.store) : undefined, }; }, @@ -13562,7 +13807,7 @@ export const SetSecretRequest: MessageFns = { if (message.value !== "") { obj.value = message.value; } - if (message.store !== 0) { + if (message.store !== undefined) { obj.store = secretStoreToJSON(message.store); } return obj; @@ -13575,13 +13820,13 @@ export const SetSecretRequest: MessageFns = { const message = createBaseSetSecretRequest(); message.key = object.key ?? ""; message.value = object.value ?? ""; - message.store = object.store ?? 0; + message.store = object.store ?? undefined; return message; }, }; function createBaseDeleteSecretRequest(): DeleteSecretRequest { - return { key: "" }; + return { key: "", store: undefined }; } export const DeleteSecretRequest: MessageFns = { @@ -13589,6 +13834,9 @@ export const DeleteSecretRequest: MessageFns = { if (message.key !== "") { writer.uint32(10).string(message.key); } + if (message.store !== undefined) { + writer.uint32(16).int32(message.store); + } return writer; }, @@ -13607,6 +13855,14 @@ export const DeleteSecretRequest: MessageFns = { message.key = reader.string(); continue; } + case 2: { + if (tag !== 16) { + break; + } + + message.store = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -13617,7 +13873,10 @@ export const DeleteSecretRequest: MessageFns = { }, fromJSON(object: any): DeleteSecretRequest { - return { key: isSet(object.key) ? globalThis.String(object.key) : "" }; + return { + key: isSet(object.key) ? globalThis.String(object.key) : "", + store: isSet(object.store) ? secretStoreFromJSON(object.store) : undefined, + }; }, toJSON(message: DeleteSecretRequest): unknown { @@ -13625,6 +13884,9 @@ export const DeleteSecretRequest: MessageFns = { if (message.key !== "") { obj.key = message.key; } + if (message.store !== undefined) { + obj.store = secretStoreToJSON(message.store); + } return obj; }, @@ -13634,6 +13896,7 @@ export const DeleteSecretRequest: MessageFns = { fromPartial(object: DeepPartial): DeleteSecretRequest { const message = createBaseDeleteSecretRequest(); message.key = object.key ?? ""; + message.store = object.store ?? undefined; return message; }, }; 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 be2efb7f..0b4896fb 100644 --- a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py +++ b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: abbenay/v1/service.proto -# Protobuf Python Version: 6.31.1 +# Protobuf Python Version: 7.35.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -11,8 +11,8 @@ from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 6, - 31, + 7, + 35, 1, '', 'abbenay/v1/service.proto' @@ -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\"\xcb\x02\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\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_url\"\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\"s\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\x42\x10\n\x0e_inline_policyB\x0f\n\r_mcp_register\"]\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\"\x91\x02\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\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_path\",\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\"\x1f\n\x10GetSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"\"\n\x11GetSecretResponse\x12\r\n\x05value\x18\x01 \x01(\t\"V\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.SecretStore\"\"\n\x13\x44\x65leteSecretRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"\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*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') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -57,20 +57,20 @@ _globals['_MCPTRANSPORT_HEADERSENTRY']._serialized_options = b'8\001' _globals['_MCPTRANSPORT_ENVENTRY']._loaded_options = None _globals['_MCPTRANSPORT_ENVENTRY']._serialized_options = b'8\001' - _globals['_ROLE']._serialized_start=18000 - _globals['_ROLE']._serialized_end=18095 - _globals['_CLIENTTYPE']._serialized_start=18098 - _globals['_CLIENTTYPE']._serialized_end=18253 - _globals['_REPLAYFORMAT']._serialized_start=18256 - _globals['_REPLAYFORMAT']._serialized_end=18386 - _globals['_MODELSOURCE']._serialized_start=18388 - _globals['_MODELSOURCE']._serialized_end=18484 - _globals['_PROVIDERTYPE']._serialized_start=18486 - _globals['_PROVIDERTYPE']._serialized_end=18603 - _globals['_LOGLEVEL']._serialized_start=18606 - _globals['_LOGLEVEL']._serialized_end=18746 - _globals['_SECRETSTORE']._serialized_start=18748 - _globals['_SECRETSTORE']._serialized_end=18865 + _globals['_ROLE']._serialized_start=18516 + _globals['_ROLE']._serialized_end=18611 + _globals['_CLIENTTYPE']._serialized_start=18614 + _globals['_CLIENTTYPE']._serialized_end=18769 + _globals['_REPLAYFORMAT']._serialized_start=18772 + _globals['_REPLAYFORMAT']._serialized_end=18902 + _globals['_MODELSOURCE']._serialized_start=18904 + _globals['_MODELSOURCE']._serialized_end=19000 + _globals['_PROVIDERTYPE']._serialized_start=19002 + _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['_EMPTY']._serialized_start=40 _globals['_EMPTY']._serialized_end=47 _globals['_TIMESTAMP']._serialized_start=49 @@ -260,169 +260,169 @@ _globals['_CONFIG_CONSUMERSENTRY']._serialized_start=9576 _globals['_CONFIG_CONSUMERSENTRY']._serialized_end=9655 _globals['_FULLPROVIDERCONFIG']._serialized_start=9658 - _globals['_FULLPROVIDERCONFIG']._serialized_end=9989 - _globals['_FULLPROVIDERCONFIG_MODELSENTRY']._serialized_start=9850 - _globals['_FULLPROVIDERCONFIG_MODELSENTRY']._serialized_end=9925 - _globals['_MODELPARAMCONFIG']._serialized_start=9992 - _globals['_MODELPARAMCONFIG']._serialized_end=10356 - _globals['_MCPSERVERCONFIGMSG']._serialized_start=10359 - _globals['_MCPSERVERCONFIGMSG']._serialized_end=10751 - _globals['_MCPSERVERCONFIGMSG_HEADERSENTRY']._serialized_start=10619 - _globals['_MCPSERVERCONFIGMSG_HEADERSENTRY']._serialized_end=10665 - _globals['_MCPSERVERCONFIGMSG_ENVENTRY']._serialized_start=10667 - _globals['_MCPSERVERCONFIGMSG_ENVENTRY']._serialized_end=10709 - _globals['_TOOLPOLICYCONFIGMSG']._serialized_start=10754 - _globals['_TOOLPOLICYCONFIGMSG']._serialized_end=11016 - _globals['_TOOLPOLICYCONFIGMSG_ALIASESENTRY']._serialized_start=10946 - _globals['_TOOLPOLICYCONFIGMSG_ALIASESENTRY']._serialized_end=10992 - _globals['_CONSUMERCONFIGMSG']._serialized_start=11019 - _globals['_CONSUMERCONFIGMSG']._serialized_end=11183 - _globals['_CONSUMERCAPABILITIESMSG']._serialized_start=11185 - _globals['_CONSUMERCAPABILITIESMSG']._serialized_end=11300 - _globals['_UPDATECONFIGREQUEST']._serialized_start=11302 - _globals['_UPDATECONFIGREQUEST']._serialized_end=11395 - _globals['_CONFIGUREPROVIDERREQUEST']._serialized_start=11398 - _globals['_CONFIGUREPROVIDERREQUEST']._serialized_end=11671 - _globals['_CONFIGUREPROVIDERRESPONSE']._serialized_start=11673 - _globals['_CONFIGUREPROVIDERRESPONSE']._serialized_end=11717 - _globals['_REMOVEPROVIDERREQUEST']._serialized_start=11719 - _globals['_REMOVEPROVIDERREQUEST']._serialized_end=11843 - _globals['_GETPROVIDERTEMPLATESREQUEST']._serialized_start=11845 - _globals['_GETPROVIDERTEMPLATESREQUEST']._serialized_end=11874 - _globals['_GETPROVIDERTEMPLATESRESPONSE']._serialized_start=11876 - _globals['_GETPROVIDERTEMPLATESRESPONSE']._serialized_end=11955 - _globals['_PROVIDERTEMPLATE']._serialized_start=11958 - _globals['_PROVIDERTEMPLATE']._serialized_end=12090 - _globals['_GETKEYSTATUSREQUEST']._serialized_start=12092 - _globals['_GETKEYSTATUSREQUEST']._serialized_end=12143 - _globals['_GETKEYSTATUSRESPONSE']._serialized_start=12145 - _globals['_GETKEYSTATUSRESPONSE']._serialized_end=12183 - _globals['_LISTMCPSERVERCONFIGSREQUEST']._serialized_start=12185 - _globals['_LISTMCPSERVERCONFIGSREQUEST']._serialized_end=12214 - _globals['_LISTMCPSERVERCONFIGSRESPONSE']._serialized_start=12216 - _globals['_LISTMCPSERVERCONFIGSRESPONSE']._serialized_end=12301 - _globals['_MCPSERVERSTATUSENTRY']._serialized_start=12304 - _globals['_MCPSERVERSTATUSENTRY']._serialized_end=12440 - _globals['_RECONNECTMCPSERVERREQUEST']._serialized_start=12442 - _globals['_RECONNECTMCPSERVERREQUEST']._serialized_end=12488 - _globals['_CREATEPOLICYREQUEST']._serialized_start=12490 - _globals['_CREATEPOLICYREQUEST']._serialized_end=12567 - _globals['_DELETEPOLICYREQUEST']._serialized_start=12569 - _globals['_DELETEPOLICYREQUEST']._serialized_end=12604 - _globals['_GETSECRETREQUEST']._serialized_start=12606 - _globals['_GETSECRETREQUEST']._serialized_end=12637 - _globals['_GETSECRETRESPONSE']._serialized_start=12639 - _globals['_GETSECRETRESPONSE']._serialized_end=12673 - _globals['_SETSECRETREQUEST']._serialized_start=12675 - _globals['_SETSECRETREQUEST']._serialized_end=12761 - _globals['_DELETESECRETREQUEST']._serialized_start=12763 - _globals['_DELETESECRETREQUEST']._serialized_end=12797 - _globals['_LISTSECRETSREQUEST']._serialized_start=12799 - _globals['_LISTSECRETSREQUEST']._serialized_end=12819 - _globals['_LISTSECRETSRESPONSE']._serialized_start=12821 - _globals['_LISTSECRETSRESPONSE']._serialized_end=12883 - _globals['_SECRETINFO']._serialized_start=12885 - _globals['_SECRETINFO']._serialized_end=12969 - _globals['_REGISTERREQUEST']._serialized_start=12971 - _globals['_REGISTERREQUEST']._serialized_end=13097 - _globals['_REGISTERRESPONSE']._serialized_start=13099 - _globals['_REGISTERRESPONSE']._serialized_end=13163 - _globals['_UNREGISTERREQUEST']._serialized_start=13165 - _globals['_UNREGISTERREQUEST']._serialized_end=13203 - _globals['_GETSTATUSREQUEST']._serialized_start=13205 - _globals['_GETSTATUSREQUEST']._serialized_end=13223 - _globals['_GETCONNECTEDWORKSPACESREQUEST']._serialized_start=13225 - _globals['_GETCONNECTEDWORKSPACESREQUEST']._serialized_end=13256 - _globals['_GETCONNECTEDWORKSPACESRESPONSE']._serialized_start=13258 - _globals['_GETCONNECTEDWORKSPACESRESPONSE']._serialized_end=13310 - _globals['_DAEMONSTATUS']._serialized_start=13313 - _globals['_DAEMONSTATUS']._serialized_end=13517 - _globals['_CONNECTEDCLIENT']._serialized_start=13520 - _globals['_CONNECTEDCLIENT']._serialized_end=13690 - _globals['_SHUTDOWNREQUEST']._serialized_start=13692 - _globals['_SHUTDOWNREQUEST']._serialized_end=13754 - _globals['_HEALTHCHECKREQUEST']._serialized_start=13756 - _globals['_HEALTHCHECKREQUEST']._serialized_end=13776 - _globals['_HEALTHCHECKRESPONSE']._serialized_start=13778 - _globals['_HEALTHCHECKRESPONSE']._serialized_end=13833 - _globals['_STARTWEBSERVERREQUEST']._serialized_start=13835 - _globals['_STARTWEBSERVERREQUEST']._serialized_end=13872 - _globals['_STARTWEBSERVERRESPONSE']._serialized_start=13874 - _globals['_STARTWEBSERVERRESPONSE']._serialized_end=13967 - _globals['_STOPWEBSERVERREQUEST']._serialized_start=13969 - _globals['_STOPWEBSERVERREQUEST']._serialized_end=13991 - _globals['_VSCODEREQUEST']._serialized_start=13994 - _globals['_VSCODEREQUEST']._serialized_end=14391 - _globals['_MODELSCHANGEDNOTIFICATION']._serialized_start=14393 - _globals['_MODELSCHANGEDNOTIFICATION']._serialized_end=14436 - _globals['_VSCODERESPONSE']._serialized_start=14439 - _globals['_VSCODERESPONSE']._serialized_end=14885 - _globals['_GETWORKSPACEREQUEST']._serialized_start=14887 - _globals['_GETWORKSPACEREQUEST']._serialized_end=14908 - _globals['_GETWORKSPACERESPONSE']._serialized_start=14910 - _globals['_GETWORKSPACERESPONSE']._serialized_end=14983 - _globals['_VSCODEERROR']._serialized_start=14985 - _globals['_VSCODEERROR']._serialized_end=15029 - _globals['_INVOKETOOLREQUEST']._serialized_start=15031 - _globals['_INVOKETOOLREQUEST']._serialized_end=15093 - _globals['_INVOKETOOLRESPONSE']._serialized_start=15095 - _globals['_INVOKETOOLRESPONSE']._serialized_end=15154 - _globals['_LISTVSCODETOOLSREQUEST']._serialized_start=15156 - _globals['_LISTVSCODETOOLSREQUEST']._serialized_end=15180 - _globals['_LISTVSCODETOOLSRESPONSE']._serialized_start=15182 - _globals['_LISTVSCODETOOLSRESPONSE']._serialized_end=15250 - _globals['_REGISTERTOOLSNOTIFICATION']._serialized_start=15252 - _globals['_REGISTERTOOLSNOTIFICATION']._serialized_end=15322 - _globals['_VSCODETOOLINFO']._serialized_start=15324 - _globals['_VSCODETOOLINFO']._serialized_end=15411 - _globals['_LISTVSCODEMODELSREQUEST']._serialized_start=15413 - _globals['_LISTVSCODEMODELSREQUEST']._serialized_end=15484 - _globals['_LISTVSCODEMODELSRESPONSE']._serialized_start=15486 - _globals['_LISTVSCODEMODELSRESPONSE']._serialized_end=15553 - _globals['_VSCODEMODEL']._serialized_start=15555 - _globals['_VSCODEMODEL']._serialized_end=15652 - _globals['_SENDVSCODECHATREQUEST']._serialized_start=15655 - _globals['_SENDVSCODECHATREQUEST']._serialized_end=15817 - _globals['_SENDVSCODECHATRESPONSE']._serialized_start=15819 - _globals['_SENDVSCODECHATRESPONSE']._serialized_end=15888 - _globals['_VSCODECHATCHUNK']._serialized_start=15890 - _globals['_VSCODECHATCHUNK']._serialized_end=15981 - _globals['_VSCODETOOLCALL']._serialized_start=15983 - _globals['_VSCODETOOLCALL']._serialized_end=16054 - _globals['_LISTPOLICIESREQUEST']._serialized_start=16056 - _globals['_LISTPOLICIESREQUEST']._serialized_end=16077 - _globals['_LISTPOLICIESRESPONSE']._serialized_start=16079 - _globals['_LISTPOLICIESRESPONSE']._serialized_end=16143 - _globals['_POLICYINFO']._serialized_start=16145 - _globals['_POLICYINFO']._serialized_end=16230 - _globals['_POLICYCONFIG']._serialized_start=16233 - _globals['_POLICYCONFIG']._serialized_end=16555 - _globals['_POLICYSAMPLING']._serialized_start=16557 - _globals['_POLICYSAMPLING']._serialized_end=16675 - _globals['_POLICYOUTPUT']._serialized_start=16678 - _globals['_POLICYOUTPUT']._serialized_end=16946 - _globals['_POLICYCONTEXT']._serialized_start=16949 - _globals['_POLICYCONTEXT']._serialized_end=17078 - _globals['_POLICYTOOL']._serialized_start=17080 - _globals['_POLICYTOOL']._serialized_end=17188 - _globals['_POLICYRELIABILITY']._serialized_start=17190 - _globals['_POLICYRELIABILITY']._serialized_end=17305 - _globals['_REGISTERMCPSERVERREQUEST']._serialized_start=17308 - _globals['_REGISTERMCPSERVERREQUEST']._serialized_end=17513 - _globals['_MCPTRANSPORT']._serialized_start=17516 - _globals['_MCPTRANSPORT']._serialized_end=17814 - _globals['_MCPTRANSPORT_HEADERSENTRY']._serialized_start=10619 - _globals['_MCPTRANSPORT_HEADERSENTRY']._serialized_end=10665 - _globals['_MCPTRANSPORT_ENVENTRY']._serialized_start=10667 - _globals['_MCPTRANSPORT_ENVENTRY']._serialized_end=10709 - _globals['_REGISTERMCPSERVERRESPONSE']._serialized_start=17816 - _globals['_REGISTERMCPSERVERRESPONSE']._serialized_end=17901 - _globals['_UNREGISTERMCPSERVERREQUEST']._serialized_start=17903 - _globals['_UNREGISTERMCPSERVERREQUEST']._serialized_end=17950 - _globals['_UNREGISTERMCPSERVERRESPONSE']._serialized_start=17952 - _globals['_UNREGISTERMCPSERVERRESPONSE']._serialized_end=17998 - _globals['_MCPBRIDGE']._serialized_start=18868 - _globals['_MCPBRIDGE']._serialized_end=19539 - _globals['_ABBENAY']._serialized_start=19542 - _globals['_ABBENAY']._serialized_end=23128 + _globals['_FULLPROVIDERCONFIG']._serialized_end=10100 + _globals['_FULLPROVIDERCONFIG_MODELSENTRY']._serialized_start=9928 + _globals['_FULLPROVIDERCONFIG_MODELSENTRY']._serialized_end=10003 + _globals['_MODELPARAMCONFIG']._serialized_start=10103 + _globals['_MODELPARAMCONFIG']._serialized_end=10467 + _globals['_MCPSERVERCONFIGMSG']._serialized_start=10470 + _globals['_MCPSERVERCONFIGMSG']._serialized_end=10862 + _globals['_MCPSERVERCONFIGMSG_HEADERSENTRY']._serialized_start=10730 + _globals['_MCPSERVERCONFIGMSG_HEADERSENTRY']._serialized_end=10776 + _globals['_MCPSERVERCONFIGMSG_ENVENTRY']._serialized_start=10778 + _globals['_MCPSERVERCONFIGMSG_ENVENTRY']._serialized_end=10820 + _globals['_TOOLPOLICYCONFIGMSG']._serialized_start=10865 + _globals['_TOOLPOLICYCONFIGMSG']._serialized_end=11127 + _globals['_TOOLPOLICYCONFIGMSG_ALIASESENTRY']._serialized_start=11057 + _globals['_TOOLPOLICYCONFIGMSG_ALIASESENTRY']._serialized_end=11103 + _globals['_CONSUMERCONFIGMSG']._serialized_start=11130 + _globals['_CONSUMERCONFIGMSG']._serialized_end=11294 + _globals['_CONSUMERCAPABILITIESMSG']._serialized_start=11297 + _globals['_CONSUMERCAPABILITIESMSG']._serialized_end=11580 + _globals['_UPDATECONFIGREQUEST']._serialized_start=11582 + _globals['_UPDATECONFIGREQUEST']._serialized_end=11675 + _globals['_CONFIGUREPROVIDERREQUEST']._serialized_start=11678 + _globals['_CONFIGUREPROVIDERREQUEST']._serialized_end=12062 + _globals['_CONFIGUREPROVIDERRESPONSE']._serialized_start=12064 + _globals['_CONFIGUREPROVIDERRESPONSE']._serialized_end=12108 + _globals['_REMOVEPROVIDERREQUEST']._serialized_start=12110 + _globals['_REMOVEPROVIDERREQUEST']._serialized_end=12234 + _globals['_GETPROVIDERTEMPLATESREQUEST']._serialized_start=12236 + _globals['_GETPROVIDERTEMPLATESREQUEST']._serialized_end=12265 + _globals['_GETPROVIDERTEMPLATESRESPONSE']._serialized_start=12267 + _globals['_GETPROVIDERTEMPLATESRESPONSE']._serialized_end=12346 + _globals['_PROVIDERTEMPLATE']._serialized_start=12349 + _globals['_PROVIDERTEMPLATE']._serialized_end=12481 + _globals['_GETKEYSTATUSREQUEST']._serialized_start=12483 + _globals['_GETKEYSTATUSREQUEST']._serialized_end=12534 + _globals['_GETKEYSTATUSRESPONSE']._serialized_start=12536 + _globals['_GETKEYSTATUSRESPONSE']._serialized_end=12574 + _globals['_LISTMCPSERVERCONFIGSREQUEST']._serialized_start=12576 + _globals['_LISTMCPSERVERCONFIGSREQUEST']._serialized_end=12605 + _globals['_LISTMCPSERVERCONFIGSRESPONSE']._serialized_start=12607 + _globals['_LISTMCPSERVERCONFIGSRESPONSE']._serialized_end=12692 + _globals['_MCPSERVERSTATUSENTRY']._serialized_start=12695 + _globals['_MCPSERVERSTATUSENTRY']._serialized_end=12831 + _globals['_RECONNECTMCPSERVERREQUEST']._serialized_start=12833 + _globals['_RECONNECTMCPSERVERREQUEST']._serialized_end=12879 + _globals['_CREATEPOLICYREQUEST']._serialized_start=12881 + _globals['_CREATEPOLICYREQUEST']._serialized_end=12958 + _globals['_DELETEPOLICYREQUEST']._serialized_start=12960 + _globals['_DELETEPOLICYREQUEST']._serialized_end=12995 + _globals['_GETSECRETREQUEST']._serialized_start=12997 + _globals['_GETSECRETREQUEST']._serialized_end=13083 + _globals['_GETSECRETRESPONSE']._serialized_start=13085 + _globals['_GETSECRETRESPONSE']._serialized_end=13119 + _globals['_SETSECRETREQUEST']._serialized_start=13121 + _globals['_SETSECRETREQUEST']._serialized_end=13222 + _globals['_DELETESECRETREQUEST']._serialized_start=13224 + _globals['_DELETESECRETREQUEST']._serialized_end=13313 + _globals['_LISTSECRETSREQUEST']._serialized_start=13315 + _globals['_LISTSECRETSREQUEST']._serialized_end=13335 + _globals['_LISTSECRETSRESPONSE']._serialized_start=13337 + _globals['_LISTSECRETSRESPONSE']._serialized_end=13399 + _globals['_SECRETINFO']._serialized_start=13401 + _globals['_SECRETINFO']._serialized_end=13485 + _globals['_REGISTERREQUEST']._serialized_start=13487 + _globals['_REGISTERREQUEST']._serialized_end=13613 + _globals['_REGISTERRESPONSE']._serialized_start=13615 + _globals['_REGISTERRESPONSE']._serialized_end=13679 + _globals['_UNREGISTERREQUEST']._serialized_start=13681 + _globals['_UNREGISTERREQUEST']._serialized_end=13719 + _globals['_GETSTATUSREQUEST']._serialized_start=13721 + _globals['_GETSTATUSREQUEST']._serialized_end=13739 + _globals['_GETCONNECTEDWORKSPACESREQUEST']._serialized_start=13741 + _globals['_GETCONNECTEDWORKSPACESREQUEST']._serialized_end=13772 + _globals['_GETCONNECTEDWORKSPACESRESPONSE']._serialized_start=13774 + _globals['_GETCONNECTEDWORKSPACESRESPONSE']._serialized_end=13826 + _globals['_DAEMONSTATUS']._serialized_start=13829 + _globals['_DAEMONSTATUS']._serialized_end=14033 + _globals['_CONNECTEDCLIENT']._serialized_start=14036 + _globals['_CONNECTEDCLIENT']._serialized_end=14206 + _globals['_SHUTDOWNREQUEST']._serialized_start=14208 + _globals['_SHUTDOWNREQUEST']._serialized_end=14270 + _globals['_HEALTHCHECKREQUEST']._serialized_start=14272 + _globals['_HEALTHCHECKREQUEST']._serialized_end=14292 + _globals['_HEALTHCHECKRESPONSE']._serialized_start=14294 + _globals['_HEALTHCHECKRESPONSE']._serialized_end=14349 + _globals['_STARTWEBSERVERREQUEST']._serialized_start=14351 + _globals['_STARTWEBSERVERREQUEST']._serialized_end=14388 + _globals['_STARTWEBSERVERRESPONSE']._serialized_start=14390 + _globals['_STARTWEBSERVERRESPONSE']._serialized_end=14483 + _globals['_STOPWEBSERVERREQUEST']._serialized_start=14485 + _globals['_STOPWEBSERVERREQUEST']._serialized_end=14507 + _globals['_VSCODEREQUEST']._serialized_start=14510 + _globals['_VSCODEREQUEST']._serialized_end=14907 + _globals['_MODELSCHANGEDNOTIFICATION']._serialized_start=14909 + _globals['_MODELSCHANGEDNOTIFICATION']._serialized_end=14952 + _globals['_VSCODERESPONSE']._serialized_start=14955 + _globals['_VSCODERESPONSE']._serialized_end=15401 + _globals['_GETWORKSPACEREQUEST']._serialized_start=15403 + _globals['_GETWORKSPACEREQUEST']._serialized_end=15424 + _globals['_GETWORKSPACERESPONSE']._serialized_start=15426 + _globals['_GETWORKSPACERESPONSE']._serialized_end=15499 + _globals['_VSCODEERROR']._serialized_start=15501 + _globals['_VSCODEERROR']._serialized_end=15545 + _globals['_INVOKETOOLREQUEST']._serialized_start=15547 + _globals['_INVOKETOOLREQUEST']._serialized_end=15609 + _globals['_INVOKETOOLRESPONSE']._serialized_start=15611 + _globals['_INVOKETOOLRESPONSE']._serialized_end=15670 + _globals['_LISTVSCODETOOLSREQUEST']._serialized_start=15672 + _globals['_LISTVSCODETOOLSREQUEST']._serialized_end=15696 + _globals['_LISTVSCODETOOLSRESPONSE']._serialized_start=15698 + _globals['_LISTVSCODETOOLSRESPONSE']._serialized_end=15766 + _globals['_REGISTERTOOLSNOTIFICATION']._serialized_start=15768 + _globals['_REGISTERTOOLSNOTIFICATION']._serialized_end=15838 + _globals['_VSCODETOOLINFO']._serialized_start=15840 + _globals['_VSCODETOOLINFO']._serialized_end=15927 + _globals['_LISTVSCODEMODELSREQUEST']._serialized_start=15929 + _globals['_LISTVSCODEMODELSREQUEST']._serialized_end=16000 + _globals['_LISTVSCODEMODELSRESPONSE']._serialized_start=16002 + _globals['_LISTVSCODEMODELSRESPONSE']._serialized_end=16069 + _globals['_VSCODEMODEL']._serialized_start=16071 + _globals['_VSCODEMODEL']._serialized_end=16168 + _globals['_SENDVSCODECHATREQUEST']._serialized_start=16171 + _globals['_SENDVSCODECHATREQUEST']._serialized_end=16333 + _globals['_SENDVSCODECHATRESPONSE']._serialized_start=16335 + _globals['_SENDVSCODECHATRESPONSE']._serialized_end=16404 + _globals['_VSCODECHATCHUNK']._serialized_start=16406 + _globals['_VSCODECHATCHUNK']._serialized_end=16497 + _globals['_VSCODETOOLCALL']._serialized_start=16499 + _globals['_VSCODETOOLCALL']._serialized_end=16570 + _globals['_LISTPOLICIESREQUEST']._serialized_start=16572 + _globals['_LISTPOLICIESREQUEST']._serialized_end=16593 + _globals['_LISTPOLICIESRESPONSE']._serialized_start=16595 + _globals['_LISTPOLICIESRESPONSE']._serialized_end=16659 + _globals['_POLICYINFO']._serialized_start=16661 + _globals['_POLICYINFO']._serialized_end=16746 + _globals['_POLICYCONFIG']._serialized_start=16749 + _globals['_POLICYCONFIG']._serialized_end=17071 + _globals['_POLICYSAMPLING']._serialized_start=17073 + _globals['_POLICYSAMPLING']._serialized_end=17191 + _globals['_POLICYOUTPUT']._serialized_start=17194 + _globals['_POLICYOUTPUT']._serialized_end=17462 + _globals['_POLICYCONTEXT']._serialized_start=17465 + _globals['_POLICYCONTEXT']._serialized_end=17594 + _globals['_POLICYTOOL']._serialized_start=17596 + _globals['_POLICYTOOL']._serialized_end=17704 + _globals['_POLICYRELIABILITY']._serialized_start=17706 + _globals['_POLICYRELIABILITY']._serialized_end=17821 + _globals['_REGISTERMCPSERVERREQUEST']._serialized_start=17824 + _globals['_REGISTERMCPSERVERREQUEST']._serialized_end=18029 + _globals['_MCPTRANSPORT']._serialized_start=18032 + _globals['_MCPTRANSPORT']._serialized_end=18330 + _globals['_MCPTRANSPORT_HEADERSENTRY']._serialized_start=10730 + _globals['_MCPTRANSPORT_HEADERSENTRY']._serialized_end=10776 + _globals['_MCPTRANSPORT_ENVENTRY']._serialized_start=10778 + _globals['_MCPTRANSPORT_ENVENTRY']._serialized_end=10820 + _globals['_REGISTERMCPSERVERRESPONSE']._serialized_start=18332 + _globals['_REGISTERMCPSERVERRESPONSE']._serialized_end=18417 + _globals['_UNREGISTERMCPSERVERREQUEST']._serialized_start=18419 + _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 # @@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 8fd72b3c..c55dbe5b 100644 --- a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.pyi +++ b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2.pyi @@ -189,7 +189,7 @@ class ToolPartialResult(_message.Message): IS_FINAL_FIELD_NUMBER: _ClassVar[int] content_json: str is_final: bool - def __init__(self, content_json: _Optional[str] = ..., is_final: bool = ...) -> None: ... + def __init__(self, content_json: _Optional[str] = ..., is_final: _Optional[bool] = ...) -> None: ... class ToolFinalResult(_message.Message): __slots__ = ("content", "is_error") @@ -197,7 +197,7 @@ class ToolFinalResult(_message.Message): IS_ERROR_FIELD_NUMBER: _ClassVar[int] content: _containers.RepeatedCompositeFieldContainer[ToolContent] is_error: bool - def __init__(self, content: _Optional[_Iterable[_Union[ToolContent, _Mapping]]] = ..., is_error: bool = ...) -> None: ... + def __init__(self, content: _Optional[_Iterable[_Union[ToolContent, _Mapping]]] = ..., is_error: _Optional[bool] = ...) -> None: ... class ToolContent(_message.Message): __slots__ = ("type", "text", "mime_type", "data", "uri") @@ -267,7 +267,7 @@ class CallToolsBatchRequest(_message.Message): STOP_ON_ERROR_FIELD_NUMBER: _ClassVar[int] calls: _containers.RepeatedCompositeFieldContainer[CallToolRequest] stop_on_error: bool - def __init__(self, calls: _Optional[_Iterable[_Union[CallToolRequest, _Mapping]]] = ..., stop_on_error: bool = ...) -> None: ... + def __init__(self, calls: _Optional[_Iterable[_Union[CallToolRequest, _Mapping]]] = ..., stop_on_error: _Optional[bool] = ...) -> None: ... class ListResourcesRequest(_message.Message): __slots__ = ("server_filter", "uri_prefix") @@ -337,7 +337,7 @@ class McpPromptArgument(_message.Message): name: str description: str required: bool - def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., required: bool = ...) -> None: ... + def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., required: _Optional[bool] = ...) -> None: ... class GetPromptRequest(_message.Message): __slots__ = ("name", "arguments") @@ -418,7 +418,7 @@ class ChatOptions(_message.Message): top_k: int timeout: int tool_mode: str - def __init__(self, temperature: _Optional[float] = ..., max_tokens: _Optional[int] = ..., top_p: _Optional[float] = ..., stop: _Optional[_Iterable[str]] = ..., enable_tools: bool = ..., max_tool_iterations: _Optional[int] = ..., tool_filter: _Optional[_Iterable[str]] = ..., top_k: _Optional[int] = ..., timeout: _Optional[int] = ..., tool_mode: _Optional[str] = ...) -> None: ... + def __init__(self, temperature: _Optional[float] = ..., max_tokens: _Optional[int] = ..., top_p: _Optional[float] = ..., stop: _Optional[_Iterable[str]] = ..., enable_tools: _Optional[bool] = ..., max_tool_iterations: _Optional[int] = ..., tool_filter: _Optional[_Iterable[str]] = ..., top_k: _Optional[int] = ..., timeout: _Optional[int] = ..., tool_mode: _Optional[str] = ...) -> None: ... class ChatChunk(_message.Message): __slots__ = ("text", "tool_call", "tool_result", "prompt", "usage", "error", "done") @@ -464,7 +464,7 @@ class ToolResultChunk(_message.Message): name: str content: str is_error: bool - def __init__(self, tool_call_id: _Optional[str] = ..., name: _Optional[str] = ..., content: _Optional[str] = ..., is_error: bool = ...) -> None: ... + def __init__(self, tool_call_id: _Optional[str] = ..., name: _Optional[str] = ..., content: _Optional[str] = ..., is_error: _Optional[bool] = ...) -> None: ... class PromptChunk(_message.Message): __slots__ = ("tool_call_id", "tool_name", "prompt_text", "options") @@ -592,7 +592,7 @@ class GetSessionRequest(_message.Message): INCLUDE_MESSAGES_FIELD_NUMBER: _ClassVar[int] session_id: str include_messages: bool - def __init__(self, session_id: _Optional[str] = ..., include_messages: bool = ...) -> None: ... + def __init__(self, session_id: _Optional[str] = ..., include_messages: _Optional[bool] = ...) -> None: ... class ListSessionsRequest(_message.Message): __slots__ = ("limit", "offset", "source_filter", "model_filter") @@ -706,7 +706,7 @@ class SummarizeSessionResponse(_message.Message): FROM_CACHE_FIELD_NUMBER: _ClassVar[int] summary: str from_cache: bool - def __init__(self, summary: _Optional[str] = ..., from_cache: bool = ...) -> None: ... + def __init__(self, summary: _Optional[str] = ..., from_cache: _Optional[bool] = ...) -> None: ... class ForkSessionRequest(_message.Message): __slots__ = ("session_id", "new_model", "fork_point") @@ -726,7 +726,7 @@ class ExportSessionRequest(_message.Message): session_id: str include_tool_results: bool include_metadata: bool - def __init__(self, session_id: _Optional[str] = ..., include_tool_results: bool = ..., include_metadata: bool = ...) -> None: ... + def __init__(self, session_id: _Optional[str] = ..., include_tool_results: _Optional[bool] = ..., include_metadata: _Optional[bool] = ...) -> None: ... class ExportSessionResponse(_message.Message): __slots__ = ("json_content",) @@ -740,7 +740,7 @@ class ImportSessionRequest(_message.Message): GENERATE_NEW_ID_FIELD_NUMBER: _ClassVar[int] json_content: str generate_new_id: bool - def __init__(self, json_content: _Optional[str] = ..., generate_new_id: bool = ...) -> None: ... + def __init__(self, json_content: _Optional[str] = ..., generate_new_id: _Optional[bool] = ...) -> None: ... class ListModelsRequest(_message.Message): __slots__ = ("provider_filter", "workspace_paths") @@ -824,7 +824,7 @@ class ModelCapabilities(_message.Message): supports_tools: bool supports_vision: bool context_window: int - def __init__(self, supports_streaming: bool = ..., supports_tools: bool = ..., supports_vision: bool = ..., context_window: _Optional[int] = ...) -> None: ... + def __init__(self, supports_streaming: _Optional[bool] = ..., supports_tools: _Optional[bool] = ..., supports_vision: _Optional[bool] = ..., context_window: _Optional[int] = ...) -> None: ... class ListProvidersRequest(_message.Message): __slots__ = ("workspace_paths",) @@ -856,7 +856,7 @@ class Provider(_message.Message): default_base_url: str engine: str base_url: str - def __init__(self, id: _Optional[str] = ..., configured: bool = ..., healthy: bool = ..., provider_type: _Optional[_Union[ProviderType, str]] = ..., requires_key: bool = ..., default_base_url: _Optional[str] = ..., engine: _Optional[str] = ..., base_url: _Optional[str] = ...) -> None: ... + def __init__(self, id: _Optional[str] = ..., configured: _Optional[bool] = ..., healthy: _Optional[bool] = ..., provider_type: _Optional[_Union[ProviderType, str]] = ..., requires_key: _Optional[bool] = ..., default_base_url: _Optional[str] = ..., engine: _Optional[str] = ..., base_url: _Optional[str] = ...) -> None: ... class GetProviderStatusRequest(_message.Message): __slots__ = ("provider_id",) @@ -876,7 +876,7 @@ class ProviderStatus(_message.Message): healthy: bool error: str last_check: Timestamp - def __init__(self, provider_id: _Optional[str] = ..., configured: bool = ..., healthy: bool = ..., error: _Optional[str] = ..., last_check: _Optional[_Union[Timestamp, _Mapping]] = ...) -> None: ... + def __init__(self, provider_id: _Optional[str] = ..., configured: _Optional[bool] = ..., healthy: _Optional[bool] = ..., error: _Optional[str] = ..., last_check: _Optional[_Union[Timestamp, _Mapping]] = ...) -> None: ... class ListEnginesRequest(_message.Message): __slots__ = () @@ -900,7 +900,7 @@ class Engine(_message.Message): default_base_url: str default_env_var: str display_name: str - def __init__(self, id: _Optional[str] = ..., requires_key: bool = ..., default_base_url: _Optional[str] = ..., default_env_var: _Optional[str] = ..., display_name: _Optional[str] = ...) -> None: ... + def __init__(self, id: _Optional[str] = ..., requires_key: _Optional[bool] = ..., default_base_url: _Optional[str] = ..., default_env_var: _Optional[str] = ..., display_name: _Optional[str] = ...) -> None: ... class ListToolsRequest(_message.Message): __slots__ = ("server_filter",) @@ -940,7 +940,7 @@ class ExecuteToolResponse(_message.Message): IS_ERROR_FIELD_NUMBER: _ClassVar[int] content: str is_error: bool - def __init__(self, content: _Optional[str] = ..., is_error: bool = ...) -> None: ... + def __init__(self, content: _Optional[str] = ..., is_error: _Optional[bool] = ...) -> None: ... class GetConfigRequest(_message.Message): __slots__ = ("location",) @@ -990,7 +990,7 @@ class Config(_message.Message): def __init__(self, providers: _Optional[_Mapping[str, FullProviderConfig]] = ..., mcp_servers: _Optional[_Mapping[str, McpServerConfigMsg]] = ..., tool_policy: _Optional[_Union[ToolPolicyConfigMsg, _Mapping]] = ..., consumers: _Optional[_Mapping[str, ConsumerConfigMsg]] = ...) -> None: ... class FullProviderConfig(_message.Message): - __slots__ = ("engine", "api_key_keychain_name", "api_key_env_var_name", "base_url", "models") + __slots__ = ("engine", "api_key_keychain_name", "api_key_env_var_name", "base_url", "models", "secret_name", "secret_store") class ModelsEntry(_message.Message): __slots__ = ("key", "value") KEY_FIELD_NUMBER: _ClassVar[int] @@ -1003,12 +1003,16 @@ class FullProviderConfig(_message.Message): API_KEY_ENV_VAR_NAME_FIELD_NUMBER: _ClassVar[int] BASE_URL_FIELD_NUMBER: _ClassVar[int] MODELS_FIELD_NUMBER: _ClassVar[int] + SECRET_NAME_FIELD_NUMBER: _ClassVar[int] + SECRET_STORE_FIELD_NUMBER: _ClassVar[int] engine: str api_key_keychain_name: str api_key_env_var_name: str base_url: str models: _containers.MessageMap[str, ModelParamConfig] - def __init__(self, engine: _Optional[str] = ..., api_key_keychain_name: _Optional[str] = ..., api_key_env_var_name: _Optional[str] = ..., base_url: _Optional[str] = ..., models: _Optional[_Mapping[str, ModelParamConfig]] = ...) -> None: ... + secret_name: str + secret_store: SecretStore + def __init__(self, engine: _Optional[str] = ..., api_key_keychain_name: _Optional[str] = ..., api_key_env_var_name: _Optional[str] = ..., base_url: _Optional[str] = ..., models: _Optional[_Mapping[str, ModelParamConfig]] = ..., secret_name: _Optional[str] = ..., secret_store: _Optional[_Union[SecretStore, str]] = ...) -> None: ... class ModelParamConfig(_message.Message): __slots__ = ("model_id", "policy", "system_prompt", "system_prompt_mode", "temperature", "top_p", "top_k", "max_tokens", "timeout") @@ -1064,7 +1068,7 @@ class McpServerConfigMsg(_message.Message): headers: _containers.ScalarMap[str, str] env: _containers.ScalarMap[str, str] max_response_size: int - def __init__(self, command: _Optional[str] = ..., args: _Optional[_Iterable[str]] = ..., url: _Optional[str] = ..., transport: _Optional[str] = ..., enabled: bool = ..., headers: _Optional[_Mapping[str, str]] = ..., env: _Optional[_Mapping[str, str]] = ..., max_response_size: _Optional[int] = ...) -> None: ... + def __init__(self, command: _Optional[str] = ..., args: _Optional[_Iterable[str]] = ..., url: _Optional[str] = ..., transport: _Optional[str] = ..., enabled: _Optional[bool] = ..., headers: _Optional[_Mapping[str, str]] = ..., env: _Optional[_Mapping[str, str]] = ..., max_response_size: _Optional[int] = ...) -> None: ... class ToolPolicyConfigMsg(_message.Message): __slots__ = ("max_tool_iterations", "auto_approve", "require_approval", "disabled_tools", "aliases") @@ -1098,12 +1102,22 @@ class ConsumerConfigMsg(_message.Message): def __init__(self, token_env: _Optional[str] = ..., token_keychain: _Optional[str] = ..., capabilities: _Optional[_Union[ConsumerCapabilitiesMsg, _Mapping]] = ...) -> None: ... class ConsumerCapabilitiesMsg(_message.Message): - __slots__ = ("inline_policy", "mcp_register") + __slots__ = ("inline_policy", "mcp_register", "secrets", "config", "providers", "shutdown", "chat") INLINE_POLICY_FIELD_NUMBER: _ClassVar[int] MCP_REGISTER_FIELD_NUMBER: _ClassVar[int] + SECRETS_FIELD_NUMBER: _ClassVar[int] + CONFIG_FIELD_NUMBER: _ClassVar[int] + PROVIDERS_FIELD_NUMBER: _ClassVar[int] + SHUTDOWN_FIELD_NUMBER: _ClassVar[int] + CHAT_FIELD_NUMBER: _ClassVar[int] inline_policy: bool mcp_register: bool - def __init__(self, inline_policy: bool = ..., mcp_register: bool = ...) -> None: ... + secrets: bool + config: bool + providers: bool + shutdown: bool + chat: bool + def __init__(self, inline_policy: _Optional[bool] = ..., mcp_register: _Optional[bool] = ..., secrets: _Optional[bool] = ..., config: _Optional[bool] = ..., providers: _Optional[bool] = ..., shutdown: _Optional[bool] = ..., chat: _Optional[bool] = ...) -> None: ... class UpdateConfigRequest(_message.Message): __slots__ = ("config", "location") @@ -1114,7 +1128,7 @@ class UpdateConfigRequest(_message.Message): def __init__(self, config: _Optional[_Union[Config, _Mapping]] = ..., location: _Optional[str] = ...) -> None: ... class ConfigureProviderRequest(_message.Message): - __slots__ = ("provider_id", "engine", "api_key", "env_var_name", "base_url", "target", "workspace_path") + __slots__ = ("provider_id", "engine", "api_key", "env_var_name", "base_url", "target", "workspace_path", "secret_name", "secret_store") PROVIDER_ID_FIELD_NUMBER: _ClassVar[int] ENGINE_FIELD_NUMBER: _ClassVar[int] API_KEY_FIELD_NUMBER: _ClassVar[int] @@ -1122,6 +1136,8 @@ class ConfigureProviderRequest(_message.Message): BASE_URL_FIELD_NUMBER: _ClassVar[int] TARGET_FIELD_NUMBER: _ClassVar[int] WORKSPACE_PATH_FIELD_NUMBER: _ClassVar[int] + SECRET_NAME_FIELD_NUMBER: _ClassVar[int] + SECRET_STORE_FIELD_NUMBER: _ClassVar[int] provider_id: str engine: str api_key: str @@ -1129,13 +1145,15 @@ class ConfigureProviderRequest(_message.Message): base_url: str target: str workspace_path: str - def __init__(self, provider_id: _Optional[str] = ..., engine: _Optional[str] = ..., api_key: _Optional[str] = ..., env_var_name: _Optional[str] = ..., base_url: _Optional[str] = ..., target: _Optional[str] = ..., workspace_path: _Optional[str] = ...) -> None: ... + secret_name: str + secret_store: SecretStore + def __init__(self, provider_id: _Optional[str] = ..., engine: _Optional[str] = ..., api_key: _Optional[str] = ..., env_var_name: _Optional[str] = ..., base_url: _Optional[str] = ..., target: _Optional[str] = ..., workspace_path: _Optional[str] = ..., secret_name: _Optional[str] = ..., secret_store: _Optional[_Union[SecretStore, str]] = ...) -> None: ... class ConfigureProviderResponse(_message.Message): __slots__ = ("success",) SUCCESS_FIELD_NUMBER: _ClassVar[int] success: bool - def __init__(self, success: bool = ...) -> None: ... + def __init__(self, success: _Optional[bool] = ...) -> None: ... class RemoveProviderRequest(_message.Message): __slots__ = ("provider_id", "target", "workspace_path") @@ -1167,7 +1185,7 @@ class ProviderTemplate(_message.Message): suggested_name: str default_base_url: str requires_key: bool - def __init__(self, engine: _Optional[str] = ..., suggested_name: _Optional[str] = ..., default_base_url: _Optional[str] = ..., requires_key: bool = ...) -> None: ... + def __init__(self, engine: _Optional[str] = ..., suggested_name: _Optional[str] = ..., default_base_url: _Optional[str] = ..., requires_key: _Optional[bool] = ...) -> None: ... class GetKeyStatusRequest(_message.Message): __slots__ = ("source", "name") @@ -1181,7 +1199,7 @@ class GetKeyStatusResponse(_message.Message): __slots__ = ("exists",) EXISTS_FIELD_NUMBER: _ClassVar[int] exists: bool - def __init__(self, exists: bool = ...) -> None: ... + def __init__(self, exists: _Optional[bool] = ...) -> None: ... class ListMcpServerConfigsRequest(_message.Message): __slots__ = () @@ -1207,7 +1225,7 @@ class McpServerStatusEntry(_message.Message): status: str tool_count: int error: str - def __init__(self, id: _Optional[str] = ..., transport: _Optional[str] = ..., enabled: bool = ..., status: _Optional[str] = ..., tool_count: _Optional[int] = ..., error: _Optional[str] = ...) -> None: ... + def __init__(self, id: _Optional[str] = ..., transport: _Optional[str] = ..., enabled: _Optional[bool] = ..., status: _Optional[str] = ..., tool_count: _Optional[int] = ..., error: _Optional[str] = ...) -> None: ... class ReconnectMcpServerRequest(_message.Message): __slots__ = ("server_id",) @@ -1230,10 +1248,12 @@ class DeletePolicyRequest(_message.Message): def __init__(self, name: _Optional[str] = ...) -> None: ... class GetSecretRequest(_message.Message): - __slots__ = ("key",) + __slots__ = ("key", "store") KEY_FIELD_NUMBER: _ClassVar[int] + STORE_FIELD_NUMBER: _ClassVar[int] key: str - def __init__(self, key: _Optional[str] = ...) -> None: ... + store: SecretStore + def __init__(self, key: _Optional[str] = ..., store: _Optional[_Union[SecretStore, str]] = ...) -> None: ... class GetSecretResponse(_message.Message): __slots__ = ("value",) @@ -1252,10 +1272,12 @@ class SetSecretRequest(_message.Message): def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ..., store: _Optional[_Union[SecretStore, str]] = ...) -> None: ... class DeleteSecretRequest(_message.Message): - __slots__ = ("key",) + __slots__ = ("key", "store") KEY_FIELD_NUMBER: _ClassVar[int] + STORE_FIELD_NUMBER: _ClassVar[int] key: str - def __init__(self, key: _Optional[str] = ...) -> None: ... + store: SecretStore + def __init__(self, key: _Optional[str] = ..., store: _Optional[_Union[SecretStore, str]] = ...) -> None: ... class ListSecretsRequest(_message.Message): __slots__ = () @@ -1275,7 +1297,7 @@ class SecretInfo(_message.Message): key: str store: SecretStore has_value: bool - def __init__(self, key: _Optional[str] = ..., store: _Optional[_Union[SecretStore, str]] = ..., has_value: bool = ...) -> None: ... + def __init__(self, key: _Optional[str] = ..., store: _Optional[_Union[SecretStore, str]] = ..., has_value: _Optional[bool] = ...) -> None: ... class RegisterRequest(_message.Message): __slots__ = ("client", "is_spawner", "workspace_path", "workspace_paths") @@ -1287,7 +1309,7 @@ class RegisterRequest(_message.Message): is_spawner: bool workspace_path: str workspace_paths: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, client: _Optional[_Union[ClientInfo, _Mapping]] = ..., is_spawner: bool = ..., workspace_path: _Optional[str] = ..., workspace_paths: _Optional[_Iterable[str]] = ...) -> None: ... + def __init__(self, client: _Optional[_Union[ClientInfo, _Mapping]] = ..., is_spawner: _Optional[bool] = ..., workspace_path: _Optional[str] = ..., workspace_paths: _Optional[_Iterable[str]] = ...) -> None: ... class RegisterResponse(_message.Message): __slots__ = ("client_id", "connected_clients") @@ -1345,7 +1367,7 @@ class ConnectedClient(_message.Message): connected_at: Timestamp is_spawner: bool workspace_path: str - def __init__(self, client_id: _Optional[str] = ..., client_type: _Optional[_Union[ClientType, str]] = ..., connected_at: _Optional[_Union[Timestamp, _Mapping]] = ..., is_spawner: bool = ..., workspace_path: _Optional[str] = ...) -> None: ... + def __init__(self, client_id: _Optional[str] = ..., client_type: _Optional[_Union[ClientType, str]] = ..., connected_at: _Optional[_Union[Timestamp, _Mapping]] = ..., is_spawner: _Optional[bool] = ..., workspace_path: _Optional[str] = ...) -> None: ... class ShutdownRequest(_message.Message): __slots__ = ("force", "grace_period_seconds") @@ -1353,7 +1375,7 @@ class ShutdownRequest(_message.Message): GRACE_PERIOD_SECONDS_FIELD_NUMBER: _ClassVar[int] force: bool grace_period_seconds: int - def __init__(self, force: bool = ..., grace_period_seconds: _Optional[int] = ...) -> None: ... + def __init__(self, force: _Optional[bool] = ..., grace_period_seconds: _Optional[int] = ...) -> None: ... class HealthCheckRequest(_message.Message): __slots__ = () @@ -1365,7 +1387,7 @@ class HealthCheckResponse(_message.Message): VERSION_FIELD_NUMBER: _ClassVar[int] healthy: bool version: str - def __init__(self, healthy: bool = ..., version: _Optional[str] = ...) -> None: ... + def __init__(self, healthy: _Optional[bool] = ..., version: _Optional[str] = ...) -> None: ... class StartWebServerRequest(_message.Message): __slots__ = ("port",) @@ -1383,7 +1405,7 @@ class StartWebServerResponse(_message.Message): already_running: bool port: int url: str - def __init__(self, started: bool = ..., already_running: bool = ..., port: _Optional[int] = ..., url: _Optional[str] = ...) -> None: ... + def __init__(self, started: _Optional[bool] = ..., already_running: _Optional[bool] = ..., port: _Optional[int] = ..., url: _Optional[str] = ...) -> None: ... class StopWebServerRequest(_message.Message): __slots__ = () @@ -1467,7 +1489,7 @@ class InvokeToolResponse(_message.Message): IS_ERROR_FIELD_NUMBER: _ClassVar[int] result_json: str is_error: bool - def __init__(self, result_json: _Optional[str] = ..., is_error: bool = ...) -> None: ... + def __init__(self, result_json: _Optional[str] = ..., is_error: _Optional[bool] = ...) -> None: ... class ListVSCodeToolsRequest(_message.Message): __slots__ = () @@ -1577,7 +1599,7 @@ class PolicyInfo(_message.Message): name: str builtin: bool config: PolicyConfig - def __init__(self, name: _Optional[str] = ..., builtin: bool = ..., config: _Optional[_Union[PolicyConfig, _Mapping]] = ...) -> None: ... + def __init__(self, name: _Optional[str] = ..., builtin: _Optional[bool] = ..., config: _Optional[_Union[PolicyConfig, _Mapping]] = ...) -> None: ... class PolicyConfig(_message.Message): __slots__ = ("sampling", "output", "context", "tool", "reliability") @@ -1639,7 +1661,7 @@ class PolicyReliability(_message.Message): TIMEOUT_FIELD_NUMBER: _ClassVar[int] retry_on_invalid_json: bool timeout: int - def __init__(self, retry_on_invalid_json: bool = ..., timeout: _Optional[int] = ...) -> None: ... + def __init__(self, retry_on_invalid_json: _Optional[bool] = ..., timeout: _Optional[int] = ...) -> None: ... class RegisterMcpServerRequest(_message.Message): __slots__ = ("server_id", "transport", "session_id", "tool_filter", "max_response_size") @@ -1693,7 +1715,7 @@ class RegisterMcpServerResponse(_message.Message): success: bool error: str discovered_tools: _containers.RepeatedScalarFieldContainer[str] - def __init__(self, success: bool = ..., error: _Optional[str] = ..., discovered_tools: _Optional[_Iterable[str]] = ...) -> None: ... + def __init__(self, success: _Optional[bool] = ..., error: _Optional[str] = ..., discovered_tools: _Optional[_Iterable[str]] = ...) -> None: ... class UnregisterMcpServerRequest(_message.Message): __slots__ = ("server_id",) @@ -1705,4 +1727,4 @@ class UnregisterMcpServerResponse(_message.Message): __slots__ = ("success",) SUCCESS_FIELD_NUMBER: _ClassVar[int] success: bool - def __init__(self, success: bool = ...) -> None: ... + def __init__(self, success: _Optional[bool] = ...) -> None: ... diff --git a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2_grpc.py b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2_grpc.py index 8a821554..b111770a 100644 --- a/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2_grpc.py +++ b/packages/python/src/abbenay_grpc/abbenay/v1/service_pb2_grpc.py @@ -5,7 +5,7 @@ from abbenay_grpc.abbenay.v1 import service_pb2 as abbenay_dot_v1_dot_service__pb2 -GRPC_GENERATED_VERSION = '1.78.0' +GRPC_GENERATED_VERSION = '1.83.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False @@ -25,7 +25,7 @@ ) -class McpBridgeStub(object): +class McpBridgeStub: """ MCP Bridge Service - High-performance tool execution @@ -86,7 +86,7 @@ def __init__(self, channel): _registered_method=True) -class McpBridgeServicer(object): +class McpBridgeServicer: """ MCP Bridge Service - High-performance tool execution @@ -208,7 +208,7 @@ def add_McpBridgeServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. -class McpBridge(object): +class McpBridge: """ MCP Bridge Service - High-performance tool execution @@ -438,7 +438,7 @@ def GetPrompt(request, _registered_method=True) -class AbbenayStub(object): +class AbbenayStub: """ Main Service - Unified daemon API @@ -681,7 +681,7 @@ def __init__(self, channel): _registered_method=True) -class AbbenayServicer(object): +class AbbenayServicer: """ Main Service - Unified daemon API @@ -1290,7 +1290,7 @@ def add_AbbenayServicer_to_server(servicer, server): # This class is part of an EXPERIMENTAL API. -class Abbenay(object): +class Abbenay: """ Main Service - Unified daemon API diff --git a/packages/vscode/src/proto/abbenay/v1/service.ts b/packages/vscode/src/proto/abbenay/v1/service.ts index 44944e10..a69bcb11 100644 --- a/packages/vscode/src/proto/abbenay/v1/service.ts +++ b/packages/vscode/src/proto/abbenay/v1/service.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.11.5 -// protoc v3.19.6 +// protoc v6.33.4 // source: abbenay/v1/service.proto /* eslint-disable */ @@ -1089,10 +1089,20 @@ export interface Config_ConsumersEntry { export interface FullProviderConfig { engine: string; - apiKeyKeychainName?: string | undefined; + /** deprecated: use secret_name */ + apiKeyKeychainName?: + | string + | undefined; + /** deprecated: use secret_name + secret_store=env */ apiKeyEnvVarName?: string | undefined; baseUrl?: string | undefined; models: { [key: string]: ModelParamConfig }; + /** Logical name (store key or env var) */ + secretName?: + | string + | undefined; + /** MEMORY | KEYCHAIN | ENV */ + secretStore?: SecretStore | undefined; } export interface FullProviderConfig_ModelsEntry { @@ -1189,7 +1199,27 @@ export interface ConsumerCapabilitiesMsg { | boolean | undefined; /** Allow dynamic MCP server registration */ - mcpRegister?: boolean | undefined; + mcpRegister?: + | boolean + | undefined; + /** Allow secret read/write/list RPCs */ + secrets?: + | boolean + | undefined; + /** Allow GetConfig / UpdateConfig / policy / web control */ + config?: + | boolean + | undefined; + /** Allow ConfigureProvider / RemoveProvider / DiscoverModels */ + providers?: + | boolean + | undefined; + /** Allow Shutdown RPC */ + shutdown?: + | boolean + | undefined; + /** Allow Chat / SessionChat */ + chat?: boolean | undefined; } export interface UpdateConfigRequest { @@ -1207,7 +1237,7 @@ export interface ConfigureProviderRequest { engine?: | string | undefined; - /** Raw API key (daemon stores in keychain) */ + /** Raw API key value (optional with secret_name) */ apiKey?: | string | undefined; @@ -1224,7 +1254,22 @@ export interface ConfigureProviderRequest { | string | undefined; /** Required if target is "workspace" */ - workspacePath?: string | undefined; + workspacePath?: + | string + | undefined; + /** + * Logical secret name (SetSecret key). Prefer this over inventing a name from + * provider_id. With api_key: store the value under this name. Without api_key: + * reference an existing secret (N providers may share one key). + */ + secretName?: + | string + | undefined; + /** + * Where to write api_key when provided (MEMORY or KEYCHAIN). Default KEYCHAIN. + * Ignored when only referencing an existing secret_name. + */ + secretStore?: SecretStore | undefined; } export interface ConfigureProviderResponse { @@ -1298,6 +1343,8 @@ export interface DeletePolicyRequest { export interface GetSecretRequest { key: string; + /** Backend namespace (default keychain) */ + store?: SecretStore | undefined; } export interface GetSecretResponse { @@ -1307,12 +1354,14 @@ export interface GetSecretResponse { export interface SetSecretRequest { key: string; value: string; - /** Where to store */ - store: SecretStore; + /** Where to store (default keychain) */ + store?: SecretStore | undefined; } export interface DeleteSecretRequest { key: string; + /** Backend namespace (default keychain) */ + store?: SecretStore | undefined; } export interface ListSecretsRequest { @@ -10868,7 +10917,15 @@ export const Config_ConsumersEntry: MessageFns = { }; function createBaseFullProviderConfig(): FullProviderConfig { - return { engine: "", apiKeyKeychainName: undefined, apiKeyEnvVarName: undefined, baseUrl: undefined, models: {} }; + return { + engine: "", + apiKeyKeychainName: undefined, + apiKeyEnvVarName: undefined, + baseUrl: undefined, + models: {}, + secretName: undefined, + secretStore: undefined, + }; } export const FullProviderConfig: MessageFns = { @@ -10888,6 +10945,12 @@ export const FullProviderConfig: MessageFns = { globalThis.Object.entries(message.models).forEach(([key, value]: [string, ModelParamConfig]) => { FullProviderConfig_ModelsEntry.encode({ key: key as any, value }, writer.uint32(42).fork()).join(); }); + if (message.secretName !== undefined) { + writer.uint32(50).string(message.secretName); + } + if (message.secretStore !== undefined) { + writer.uint32(56).int32(message.secretStore); + } return writer; }, @@ -10941,6 +11004,22 @@ export const FullProviderConfig: MessageFns = { } continue; } + case 6: { + if (tag !== 50) { + break; + } + + message.secretName = reader.string(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.secretStore = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -10977,6 +11056,16 @@ export const FullProviderConfig: MessageFns = { {}, ) : {}, + secretName: isSet(object.secretName) + ? globalThis.String(object.secretName) + : isSet(object.secret_name) + ? globalThis.String(object.secret_name) + : undefined, + secretStore: isSet(object.secretStore) + ? secretStoreFromJSON(object.secretStore) + : isSet(object.secret_store) + ? secretStoreFromJSON(object.secret_store) + : undefined, }; }, @@ -11003,6 +11092,12 @@ export const FullProviderConfig: MessageFns = { }); } } + if (message.secretName !== undefined) { + obj.secretName = message.secretName; + } + if (message.secretStore !== undefined) { + obj.secretStore = secretStoreToJSON(message.secretStore); + } return obj; }, @@ -11024,6 +11119,8 @@ export const FullProviderConfig: MessageFns = { }, {}, ); + message.secretName = object.secretName ?? undefined; + message.secretStore = object.secretStore ?? undefined; return message; }, }; @@ -12059,7 +12156,15 @@ export const ConsumerConfigMsg: MessageFns = { }; function createBaseConsumerCapabilitiesMsg(): ConsumerCapabilitiesMsg { - return { inlinePolicy: undefined, mcpRegister: undefined }; + return { + inlinePolicy: undefined, + mcpRegister: undefined, + secrets: undefined, + config: undefined, + providers: undefined, + shutdown: undefined, + chat: undefined, + }; } export const ConsumerCapabilitiesMsg: MessageFns = { @@ -12070,6 +12175,21 @@ export const ConsumerCapabilitiesMsg: MessageFns = { if (message.mcpRegister !== undefined) { writer.uint32(16).bool(message.mcpRegister); } + if (message.secrets !== undefined) { + writer.uint32(24).bool(message.secrets); + } + if (message.config !== undefined) { + writer.uint32(32).bool(message.config); + } + if (message.providers !== undefined) { + writer.uint32(40).bool(message.providers); + } + if (message.shutdown !== undefined) { + writer.uint32(48).bool(message.shutdown); + } + if (message.chat !== undefined) { + writer.uint32(56).bool(message.chat); + } return writer; }, @@ -12096,6 +12216,46 @@ export const ConsumerCapabilitiesMsg: MessageFns = { message.mcpRegister = reader.bool(); continue; } + case 3: { + if (tag !== 24) { + break; + } + + message.secrets = reader.bool(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.config = reader.bool(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.providers = reader.bool(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.shutdown = reader.bool(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.chat = reader.bool(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -12117,6 +12277,11 @@ export const ConsumerCapabilitiesMsg: MessageFns = { : isSet(object.mcp_register) ? globalThis.Boolean(object.mcp_register) : undefined, + secrets: isSet(object.secrets) ? globalThis.Boolean(object.secrets) : undefined, + config: isSet(object.config) ? globalThis.Boolean(object.config) : undefined, + providers: isSet(object.providers) ? globalThis.Boolean(object.providers) : undefined, + shutdown: isSet(object.shutdown) ? globalThis.Boolean(object.shutdown) : undefined, + chat: isSet(object.chat) ? globalThis.Boolean(object.chat) : undefined, }; }, @@ -12128,6 +12293,21 @@ export const ConsumerCapabilitiesMsg: MessageFns = { if (message.mcpRegister !== undefined) { obj.mcpRegister = message.mcpRegister; } + if (message.secrets !== undefined) { + obj.secrets = message.secrets; + } + if (message.config !== undefined) { + obj.config = message.config; + } + if (message.providers !== undefined) { + obj.providers = message.providers; + } + if (message.shutdown !== undefined) { + obj.shutdown = message.shutdown; + } + if (message.chat !== undefined) { + obj.chat = message.chat; + } return obj; }, @@ -12138,6 +12318,11 @@ export const ConsumerCapabilitiesMsg: MessageFns = { const message = createBaseConsumerCapabilitiesMsg(); message.inlinePolicy = object.inlinePolicy ?? undefined; message.mcpRegister = object.mcpRegister ?? undefined; + message.secrets = object.secrets ?? undefined; + message.config = object.config ?? undefined; + message.providers = object.providers ?? undefined; + message.shutdown = object.shutdown ?? undefined; + message.chat = object.chat ?? undefined; return message; }, }; @@ -12229,6 +12414,8 @@ function createBaseConfigureProviderRequest(): ConfigureProviderRequest { baseUrl: undefined, target: undefined, workspacePath: undefined, + secretName: undefined, + secretStore: undefined, }; } @@ -12255,6 +12442,12 @@ export const ConfigureProviderRequest: MessageFns = { if (message.workspacePath !== undefined) { writer.uint32(58).string(message.workspacePath); } + if (message.secretName !== undefined) { + writer.uint32(66).string(message.secretName); + } + if (message.secretStore !== undefined) { + writer.uint32(72).int32(message.secretStore); + } return writer; }, @@ -12321,6 +12514,22 @@ export const ConfigureProviderRequest: MessageFns = { message.workspacePath = reader.string(); continue; } + case 8: { + if (tag !== 66) { + break; + } + + message.secretName = reader.string(); + continue; + } + case 9: { + if (tag !== 72) { + break; + } + + message.secretStore = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -12359,6 +12568,16 @@ export const ConfigureProviderRequest: MessageFns = { : isSet(object.workspace_path) ? globalThis.String(object.workspace_path) : undefined, + secretName: isSet(object.secretName) + ? globalThis.String(object.secretName) + : isSet(object.secret_name) + ? globalThis.String(object.secret_name) + : undefined, + secretStore: isSet(object.secretStore) + ? secretStoreFromJSON(object.secretStore) + : isSet(object.secret_store) + ? secretStoreFromJSON(object.secret_store) + : undefined, }; }, @@ -12385,6 +12604,12 @@ export const ConfigureProviderRequest: MessageFns = { if (message.workspacePath !== undefined) { obj.workspacePath = message.workspacePath; } + if (message.secretName !== undefined) { + obj.secretName = message.secretName; + } + if (message.secretStore !== undefined) { + obj.secretStore = secretStoreToJSON(message.secretStore); + } return obj; }, @@ -12400,6 +12625,8 @@ export const ConfigureProviderRequest: MessageFns = { message.baseUrl = object.baseUrl ?? undefined; message.target = object.target ?? undefined; message.workspacePath = object.workspacePath ?? undefined; + message.secretName = object.secretName ?? undefined; + message.secretStore = object.secretStore ?? undefined; return message; }, }; @@ -13373,7 +13600,7 @@ export const DeletePolicyRequest: MessageFns = { }; function createBaseGetSecretRequest(): GetSecretRequest { - return { key: "" }; + return { key: "", store: undefined }; } export const GetSecretRequest: MessageFns = { @@ -13381,6 +13608,9 @@ export const GetSecretRequest: MessageFns = { if (message.key !== "") { writer.uint32(10).string(message.key); } + if (message.store !== undefined) { + writer.uint32(16).int32(message.store); + } return writer; }, @@ -13399,6 +13629,14 @@ export const GetSecretRequest: MessageFns = { message.key = reader.string(); continue; } + case 2: { + if (tag !== 16) { + break; + } + + message.store = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -13409,7 +13647,10 @@ export const GetSecretRequest: MessageFns = { }, fromJSON(object: any): GetSecretRequest { - return { key: isSet(object.key) ? globalThis.String(object.key) : "" }; + return { + key: isSet(object.key) ? globalThis.String(object.key) : "", + store: isSet(object.store) ? secretStoreFromJSON(object.store) : undefined, + }; }, toJSON(message: GetSecretRequest): unknown { @@ -13417,6 +13658,9 @@ export const GetSecretRequest: MessageFns = { if (message.key !== "") { obj.key = message.key; } + if (message.store !== undefined) { + obj.store = secretStoreToJSON(message.store); + } return obj; }, @@ -13426,6 +13670,7 @@ export const GetSecretRequest: MessageFns = { fromPartial(object: DeepPartial): GetSecretRequest { const message = createBaseGetSecretRequest(); message.key = object.key ?? ""; + message.store = object.store ?? undefined; return message; }, }; @@ -13489,7 +13734,7 @@ export const GetSecretResponse: MessageFns = { }; function createBaseSetSecretRequest(): SetSecretRequest { - return { key: "", value: "", store: 0 }; + return { key: "", value: "", store: undefined }; } export const SetSecretRequest: MessageFns = { @@ -13500,7 +13745,7 @@ export const SetSecretRequest: MessageFns = { if (message.value !== "") { writer.uint32(18).string(message.value); } - if (message.store !== 0) { + if (message.store !== undefined) { writer.uint32(24).int32(message.store); } return writer; @@ -13550,7 +13795,7 @@ export const SetSecretRequest: MessageFns = { return { key: isSet(object.key) ? globalThis.String(object.key) : "", value: isSet(object.value) ? globalThis.String(object.value) : "", - store: isSet(object.store) ? secretStoreFromJSON(object.store) : 0, + store: isSet(object.store) ? secretStoreFromJSON(object.store) : undefined, }; }, @@ -13562,7 +13807,7 @@ export const SetSecretRequest: MessageFns = { if (message.value !== "") { obj.value = message.value; } - if (message.store !== 0) { + if (message.store !== undefined) { obj.store = secretStoreToJSON(message.store); } return obj; @@ -13575,13 +13820,13 @@ export const SetSecretRequest: MessageFns = { const message = createBaseSetSecretRequest(); message.key = object.key ?? ""; message.value = object.value ?? ""; - message.store = object.store ?? 0; + message.store = object.store ?? undefined; return message; }, }; function createBaseDeleteSecretRequest(): DeleteSecretRequest { - return { key: "" }; + return { key: "", store: undefined }; } export const DeleteSecretRequest: MessageFns = { @@ -13589,6 +13834,9 @@ export const DeleteSecretRequest: MessageFns = { if (message.key !== "") { writer.uint32(10).string(message.key); } + if (message.store !== undefined) { + writer.uint32(16).int32(message.store); + } return writer; }, @@ -13607,6 +13855,14 @@ export const DeleteSecretRequest: MessageFns = { message.key = reader.string(); continue; } + case 2: { + if (tag !== 16) { + break; + } + + message.store = reader.int32() as any; + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -13617,7 +13873,10 @@ export const DeleteSecretRequest: MessageFns = { }, fromJSON(object: any): DeleteSecretRequest { - return { key: isSet(object.key) ? globalThis.String(object.key) : "" }; + return { + key: isSet(object.key) ? globalThis.String(object.key) : "", + store: isSet(object.store) ? secretStoreFromJSON(object.store) : undefined, + }; }, toJSON(message: DeleteSecretRequest): unknown { @@ -13625,6 +13884,9 @@ export const DeleteSecretRequest: MessageFns = { if (message.key !== "") { obj.key = message.key; } + if (message.store !== undefined) { + obj.store = secretStoreToJSON(message.store); + } return obj; }, @@ -13634,6 +13896,7 @@ export const DeleteSecretRequest: MessageFns = { fromPartial(object: DeepPartial): DeleteSecretRequest { const message = createBaseDeleteSecretRequest(); message.key = object.key ?? ""; + message.store = object.store ?? undefined; return message; }, }; diff --git a/proto/abbenay/v1/service.proto b/proto/abbenay/v1/service.proto index c626df87..024dfcdb 100644 --- a/proto/abbenay/v1/service.proto +++ b/proto/abbenay/v1/service.proto @@ -854,10 +854,12 @@ message Config { message FullProviderConfig { string engine = 1; - optional string api_key_keychain_name = 2; - optional string api_key_env_var_name = 3; + optional string api_key_keychain_name = 2; // deprecated: use secret_name + optional string api_key_env_var_name = 3; // deprecated: use secret_name + secret_store=env optional string base_url = 4; map models = 5; + optional string secret_name = 6; // Logical name (store key or env var) + optional SecretStore secret_store = 7; // MEMORY | KEYCHAIN | ENV } message ModelParamConfig { @@ -928,11 +930,18 @@ message UpdateConfigRequest { message ConfigureProviderRequest { string provider_id = 1; // Virtual provider name optional string engine = 2; // Engine type (e.g., "openai") - optional string api_key = 3; // Raw API key (daemon stores in keychain) + optional string api_key = 3; // Raw API key value (optional with secret_name) optional string env_var_name = 4; // Alternative: use env var for API key optional string base_url = 5; // Custom API base URL optional string target = 6; // "user" (default) or "workspace" optional string workspace_path = 7; // Required if target is "workspace" + // Logical secret name (SetSecret key). Prefer this over inventing a name from + // provider_id. With api_key: store the value under this name. Without api_key: + // reference an existing secret (N providers may share one key). + optional string secret_name = 8; + // Where to write api_key when provided (MEMORY or KEYCHAIN). Default KEYCHAIN. + // Ignored when only referencing an existing secret_name. + optional SecretStore secret_store = 9; } message ConfigureProviderResponse { @@ -1013,6 +1022,7 @@ message DeletePolicyRequest { message GetSecretRequest { string key = 1; + optional SecretStore store = 2; // Backend namespace (default keychain) } message GetSecretResponse { @@ -1022,7 +1032,7 @@ message GetSecretResponse { message SetSecretRequest { string key = 1; string value = 2; - SecretStore store = 3; // Where to store + optional SecretStore store = 3; // Where to store (default keychain) } enum SecretStore { @@ -1034,6 +1044,7 @@ enum SecretStore { message DeleteSecretRequest { string key = 1; + optional SecretStore store = 2; // Backend namespace (default keychain) } message ListSecretsRequest {} diff --git a/sonar-project.properties b/sonar-project.properties index 1e5dae08..14f07bb1 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -15,6 +15,9 @@ sonar.sources=packages/daemon/src/,packages/vscode/src/ sonar.tests=packages/daemon/src/,packages/daemon/tests/,packages/vscode/src/test/ # Keep source/test sets disjoint: co-located *.test.ts live under source roots # but must be classified only as tests (not main sources). -sonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/**,packages/proto-ts/**,packages/python/**,**/*.test.ts +# Generated proto stubs under vscode mirror packages/proto-ts (already excluded). +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/** sonar.verbose=false