@jini-ai/agent-runtime 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.4.8 — 2026-10-13
2
+
3
+ - The root import no longer needs the optional `@jini-ai/integrations` peer: the media-provider catalog loads lazily, only when a default provider URL is needed. A missing peer at that point throws an actionable error with the import failure as its `cause`; explicit `*_BASE_URL` env values never touch it.
4
+ - New `createElevenLabsVoiceClient({ http, clock }, { maxCacheEntries })`: voices listed through an injected `HttpClientPort`, expired entries swept, oldest evicted past the bound, `clearCache()`. `listElevenLabsVoiceOptions` stays as the compatibility wrapper over native fetch.
5
+
6
+ ## 0.4.7 — 2026-10-13
7
+
8
+ - New `createNativeAgentRuntime({ env }, { state, acpModelProbe, codexDisablePlugins, piGracefulShutdownMs })`: one host-owned runtime state with detection, launch and definitions bound to it, environment policies resolved once, and `dispose({})` that drains in-flight discovery before clearing caches.
9
+ - ACP model probes can come from the host (`AgentRuntimeState.acpModelProbe`, `fetchModels(required, { acpModelProbe })`); `setAcpModelProbe` stays as the compatibility setter for unbound definitions.
10
+ - Codex `--disable plugins` and the pi RPC shutdown grace period are explicit (`RuntimeBuildOptions.codexDisablePlugins`, `PiRpcSessionOptions.gracefulShutdownMs`); `CODEX_DISABLE_PLUGINS` / `PI_GRACEFUL_SHUTDOWN_MS` are read at native composition instead of inside `buildArgs` / the session.
11
+
1
12
  ## 0.4.6 — 2026-10-13
2
13
 
3
14
  - New `./model-discovery` subpath: the discovery policy with injected `deps` and `probes` (`createAgentModelDiscovery({ agentId, configuration, deps, probes })`, `modelDiscoveryForDef({ def, deps })`, `resolveModelForLaunch({ def, context, deps })`). It loads no child_process/fs or native adapters. The root keeps the published scalar signatures, wired to the native probes.
@@ -12,18 +12,29 @@ export interface AcpModelProbeRequest {
12
12
  cwd?: string;
13
13
  env?: RuntimeEnv;
14
14
  timeoutMs?: number;
15
+ signal?: AbortSignal;
15
16
  clientName?: string;
16
17
  clientVersion?: string;
17
18
  defaultModelOption?: RuntimeModelOption;
18
19
  }
19
20
  export interface AcpModelProbe {
20
21
  detectModels(requiredArgs: Pick<AcpModelProbeRequest, 'bin' | 'args'>, optionalArgs?: Omit<AcpModelProbeRequest, 'bin' | 'args'>): Promise<RuntimeModelOption[]>;
22
+ /** Optional metadata capability preserves a native current selection without guessing from order. */
23
+ detectMetadata?(requiredArgs: Pick<AcpModelProbeRequest, 'bin' | 'args'>, optionalArgs?: Omit<AcpModelProbeRequest, 'bin' | 'args'>): Promise<{
24
+ models: RuntimeModelOption[];
25
+ currentModelId: string | null;
26
+ }>;
21
27
  }
22
28
  export declare const noopAcpModelProbe: AcpModelProbe;
23
- /** Install a transport/test double. Pass `null` to restore the native transport. */
29
+ /** Native adapter selected once by each host composition; independent of the compatibility setter. */
30
+ export declare const nativeAcpModelProbe: AcpModelProbe;
31
+ /** Compatibility setter for unbound definitions. Owned hosts keep their selected transport.
32
+ * Pass `null` to restore the native transport for the legacy wrapper. */
24
33
  export declare function setAcpModelProbe({ probe }: {
25
34
  probe: AcpModelProbe | null;
26
35
  }): void;
27
- /** Delegate to the currently installed probe. */
28
- export declare function detectAcpModels(requiredArgs: Pick<AcpModelProbeRequest, "bin" | "args">, optionalArgs?: Omit<AcpModelProbeRequest, "bin" | "args">): Promise<RuntimeModelOption[]>;
36
+ /** Delegate to an explicit host probe, or the currently installed compatibility probe. */
37
+ export declare function detectAcpModels(requiredArgs: Pick<AcpModelProbeRequest, "bin" | "args">, optionalArgs?: Omit<AcpModelProbeRequest, "bin" | "args"> & {
38
+ probe?: AcpModelProbe;
39
+ }): Promise<RuntimeModelOption[]>;
29
40
  //# sourceMappingURL=acp-model-probe.d.ts.map
@@ -1,17 +1,23 @@
1
- import { detectAcpModels as probeNativeModels } from './agent-protocol/acp/models.js';
1
+ import { detectAcpModels as probeNativeModels, detectAcpModelMetadata } from './agent-protocol/acp/models.js';
2
2
  export const noopAcpModelProbe = {
3
3
  async detectModels() {
4
4
  return [];
5
5
  },
6
6
  };
7
- const nativeAcpModelProbe = { detectModels: (required, optional = {}) => probeNativeModels(required, { ...optional, ...(optional.env ? { env: optional.env } : {}) }) };
7
+ /** Native adapter selected once by each host composition; independent of the compatibility setter. */
8
+ export const nativeAcpModelProbe = {
9
+ detectModels: (required, optional = {}) => probeNativeModels(required, optional),
10
+ detectMetadata: (required, optional = {}) => detectAcpModelMetadata(required, optional),
11
+ };
8
12
  let activeAcpModelProbe = nativeAcpModelProbe;
9
- /** Install a transport/test double. Pass `null` to restore the native transport. */
13
+ /** Compatibility setter for unbound definitions. Owned hosts keep their selected transport.
14
+ * Pass `null` to restore the native transport for the legacy wrapper. */
10
15
  export function setAcpModelProbe({ probe }) {
11
16
  activeAcpModelProbe = probe ?? nativeAcpModelProbe;
12
17
  }
13
- /** Delegate to the currently installed probe. */
18
+ /** Delegate to an explicit host probe, or the currently installed compatibility probe. */
14
19
  export async function detectAcpModels(requiredArgs, optionalArgs = {}) {
15
- return activeAcpModelProbe.detectModels(requiredArgs, optionalArgs);
20
+ const { probe = activeAcpModelProbe, ...request } = optionalArgs;
21
+ return probe.detectModels(requiredArgs, request);
16
22
  }
17
23
  //# sourceMappingURL=acp-model-probe.js.map
@@ -19,6 +19,8 @@ export type PiRpcSessionOptions = {
19
19
  imagePaths?: string[];
20
20
  uploadRoot?: string;
21
21
  parentSession?: string;
22
+ /** Explicit post-completion grace period before SIGTERM. Native hosts resolve env defaults. */
23
+ gracefulShutdownMs?: number;
22
24
  };
23
25
  /** Handle returned by `attachPiRpcSession` for querying run state and requesting abort. */
24
26
  export type PiRpcSession = {
@@ -248,7 +248,7 @@ export function handlePiResponseMessage({ raw, parentSessionRpcId, promptRpcId,
248
248
  * @returns A `PiRpcSession` handle with `hasFatalError`, `getLastSessionPath`, and `abort`.
249
249
  */
250
250
  export function attachPiRpcSession(requiredArgs, optionalArgs = {}) {
251
- const { child, prompt, cwd, model, send, imagePaths, uploadRoot, parentSession, } = { ...optionalArgs, ...requiredArgs };
251
+ const { child, prompt, cwd, model, send, imagePaths, uploadRoot, parentSession, gracefulShutdownMs = 5000, } = { ...optionalArgs, ...requiredArgs };
252
252
  const stdin = child.stdin;
253
253
  const stdout = child.stdout;
254
254
  if (stdin === null) {
@@ -343,13 +343,12 @@ export function attachPiRpcSession(requiredArgs, optionalArgs = {}) {
343
343
  catch (err) {
344
344
  fail(`stdin close: ${errorMessage(err)}`);
345
345
  }
346
- // Grace period before SIGTERM. Configurable via PI_GRACEFUL_SHUTDOWN_MS
347
- // for resource-constrained machines where the event loop drains slowly.
348
- const shutdownMs = Number(process.env.PI_GRACEFUL_SHUTDOWN_MS) || 5000;
346
+ // Grace period before SIGTERM lets resource-constrained machines drain a slow event loop.
347
+ // The host supplies timing explicitly, resolving PI_GRACEFUL_SHUTDOWN_MS at composition.
349
348
  setTimeout(() => {
350
349
  if (!child.killed)
351
350
  child.kill('SIGTERM');
352
- }, shutdownMs);
351
+ }, gracefulShutdownMs);
353
352
  };
354
353
  // ---- Inbound: parse stdout events ----
355
354
  const parser = createJsonLineStream({ onMessage: ({ message: raw }) => {
@@ -6,7 +6,9 @@ export declare const auggieAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
@@ -15,7 +15,8 @@ export const auggieAgentDef = {
15
15
  name: 'Auggie CLI',
16
16
  bin: 'auggie',
17
17
  versionArgs: ['--version'],
18
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
18
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
19
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
19
20
  bin: resolvedBin,
20
21
  args: ['--acp'],
21
22
  env,
@@ -6,7 +6,9 @@ export declare const clineAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
@@ -17,7 +17,8 @@ export const clineAgentDef = {
17
17
  name: 'Cline',
18
18
  bin: 'cline',
19
19
  versionArgs: ['--version'],
20
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
20
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
21
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
21
22
  bin: resolvedBin,
22
23
  args: ['--acp'],
23
24
  env,
@@ -1,6 +1,6 @@
1
1
  import { createAgentModelDiscovery } from '../model-discovery-native.js';
2
- /** CODEX_SANDBOX_MODE and CODEX_DISABLE_PLUGINS are adapter-scoped operator overrides;
3
- * no product-specific environment naming is required. */
2
+ /** CODEX_SANDBOX_MODE remains an adapter-scoped operator override; plugin policy is supplied
3
+ * explicitly by the host, whose native composition resolves CODEX_DISABLE_PLUGINS. */
4
4
  import { DEFAULT_MODEL_OPTION, clampCodexReasoning } from './shared.js';
5
5
  /**
6
6
  * The `visibility` values that mean "reachable by id, but do not render in a picker".
@@ -292,7 +292,7 @@ export const codexAgentDef = {
292
292
  const args = resumeSessionId
293
293
  ? ['exec', 'resume', '--json', '--skip-git-repo-check', ...sandboxArgs]
294
294
  : ['exec', '--json', '--skip-git-repo-check', ...sandboxArgs];
295
- if (process.env.CODEX_DISABLE_PLUGINS === '1') {
295
+ if (options.codexDisablePlugins === true) {
296
296
  args.push('--disable', 'plugins');
297
297
  }
298
298
  // `-C <cwd>` and `--add-dir <dir>` are CREATE-only flags: `codex exec
@@ -6,7 +6,9 @@ export declare const devinAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: ({ prompt: _prompt, imagePaths: _imagePaths }: {
12
14
  prompt: string;
@@ -7,7 +7,8 @@ export const devinAgentDef = {
7
7
  name: 'Devin for Terminal',
8
8
  bin: 'devin',
9
9
  versionArgs: ['--version'],
10
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
10
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
11
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
11
12
  bin: resolvedBin,
12
13
  args: [
13
14
  '--permission-mode',
@@ -6,7 +6,9 @@ export declare const gooseAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
@@ -18,7 +18,8 @@ export const gooseAgentDef = {
18
18
  name: 'goose',
19
19
  bin: 'goose',
20
20
  versionArgs: ['--version'],
21
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
21
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
22
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
22
23
  bin: resolvedBin,
23
24
  args: ['acp'],
24
25
  env,
@@ -6,7 +6,9 @@ export declare const hermesAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
@@ -7,7 +7,8 @@ export const hermesAgentDef = {
7
7
  name: 'Hermes',
8
8
  bin: 'hermes',
9
9
  versionArgs: ['--version'],
10
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
10
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
11
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
11
12
  bin: resolvedBin,
12
13
  args: ['acp', '--accept-hooks'],
13
14
  env,
@@ -6,7 +6,9 @@ export declare const kiloAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
package/dist/defs/kilo.js CHANGED
@@ -7,7 +7,8 @@ export const kiloAgentDef = {
7
7
  name: 'Kilo',
8
8
  bin: 'kilo',
9
9
  versionArgs: ['--version'],
10
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
10
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
11
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
11
12
  bin: resolvedBin,
12
13
  args: ['acp'],
13
14
  env,
@@ -7,7 +7,9 @@ export declare const kimiAgentDef: {
7
7
  fetchModels: ({ resolvedBin, env }: {
8
8
  resolvedBin: string;
9
9
  env: import("../types.js").RuntimeEnv;
10
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
10
+ }, { acpModelProbe }?: {
11
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
12
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
13
15
  mcpDiscovery: string;
package/dist/defs/kimi.js CHANGED
@@ -13,7 +13,8 @@ export const kimiAgentDef = {
13
13
  { id: 'moonshot-v1-8k', label: 'moonshot-v1-8k' },
14
14
  { id: 'moonshot-v1-32k', label: 'moonshot-v1-32k' },
15
15
  ],
16
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
16
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
17
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
17
18
  bin: resolvedBin,
18
19
  args: ['acp'],
19
20
  env,
@@ -6,7 +6,9 @@ export declare const kiroAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
package/dist/defs/kiro.js CHANGED
@@ -7,7 +7,8 @@ export const kiroAgentDef = {
7
7
  name: 'Kiro CLI',
8
8
  bin: 'kiro-cli',
9
9
  versionArgs: ['--version'],
10
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
10
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
11
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
11
12
  bin: resolvedBin,
12
13
  args: ['acp'],
13
14
  env,
@@ -7,7 +7,9 @@ export declare const reasonixAgentDef: {
7
7
  fetchModels: ({ resolvedBin, env }: {
8
8
  resolvedBin: string;
9
9
  env: import("../types.js").RuntimeEnv;
10
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
10
+ }, { acpModelProbe }?: {
11
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
12
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
11
13
  buildArgs: () => string[];
12
14
  streamFormat: string;
13
15
  mcpDiscovery: string;
@@ -24,7 +24,8 @@ export const reasonixAgentDef = {
24
24
  bin: 'reasonix',
25
25
  fallbackBins: ['dsnix'],
26
26
  versionArgs: ['--version'],
27
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
27
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
28
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
28
29
  bin: resolvedBin,
29
30
  args: ['acp'],
30
31
  env,
@@ -7,7 +7,9 @@ export declare const traeCliAgentDef: {
7
7
  fetchModels: ({ resolvedBin, env }: {
8
8
  resolvedBin: string;
9
9
  env: import("../types.js").RuntimeEnv;
10
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
10
+ }, { acpModelProbe }?: {
11
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
12
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
11
13
  fallbackModels: import("../types.js").RuntimeModelOption[];
12
14
  buildArgs: ({ prompt: _prompt, imagePaths: _imagePaths }: {
13
15
  prompt: string;
@@ -8,7 +8,8 @@ export const traeCliAgentDef = {
8
8
  bin: 'traecli',
9
9
  versionArgs: ['--version'],
10
10
  versionProbeTimeoutMs: 10_000,
11
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
11
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
12
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
12
13
  bin: resolvedBin,
13
14
  args: ['acp', 'serve'],
14
15
  env,
@@ -6,7 +6,9 @@ export declare const vibeAgentDef: {
6
6
  fetchModels: ({ resolvedBin, env }: {
7
7
  resolvedBin: string;
8
8
  env: import("../types.js").RuntimeEnv;
9
- }) => Promise<import("../types.js").RuntimeModelOption[]>;
9
+ }, { acpModelProbe }?: {
10
+ acpModelProbe?: import("../acp-model-probe.js").AcpModelProbe;
11
+ } | undefined) => Promise<import("../types.js").RuntimeModelOption[]>;
10
12
  fallbackModels: import("../types.js").RuntimeModelOption[];
11
13
  buildArgs: () => never[];
12
14
  streamFormat: string;
package/dist/defs/vibe.js CHANGED
@@ -7,7 +7,8 @@ export const vibeAgentDef = {
7
7
  name: 'Mistral Vibe CLI',
8
8
  bin: 'vibe-acp',
9
9
  versionArgs: ['--version'],
10
- fetchModels: async ({ resolvedBin, env }) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
10
+ fetchModels: async ({ resolvedBin, env }, { acpModelProbe } = {}) => (({ bin, args, ...optionalArgs }) => detectAcpModels({ bin, args }, optionalArgs))({
11
+ ...(acpModelProbe ? { probe: acpModelProbe } : {}),
11
12
  bin: resolvedBin,
12
13
  args: [],
13
14
  env,
package/dist/index.d.ts CHANGED
@@ -50,4 +50,5 @@ export { createJsonLineStream, type AcpMcpServerInput, type AcpPermissionDecisio
50
50
  export * from './model-discovery-native.js';
51
51
  export { getRuntimeModelDiscoveryDeps, defaultModelDiscoveryDeps, parseClaudeInitializeMetadata, parsePiRpcMetadata } from './model-discovery-adapters.js';
52
52
  export * from './runtime-state.js';
53
+ export * from './native-runtime.js';
53
54
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -73,4 +73,5 @@ export { createJsonLineStream, buildAcpSessionNewParams, normalizeModels, detect
73
73
  export * from './model-discovery-native.js';
74
74
  export { getRuntimeModelDiscoveryDeps, defaultModelDiscoveryDeps, parseClaudeInitializeMetadata, parsePiRpcMetadata } from './model-discovery-adapters.js';
75
75
  export * from './runtime-state.js';
76
+ export * from './native-runtime.js';
76
77
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,5 @@
1
1
  import { type AgentRuntimeState } from './runtime-state.js';
2
+ import { type AcpModelProbe } from './acp-model-probe.js';
2
3
  import type { AgentDiscoveryConfiguration, ModelDiscoveryContext, ModelDiscoveryDeps, ModelProbeResult } from './model-discovery-types.js';
3
4
  export declare function modelProbeTimeoutMs(agentId: string): number;
4
5
  /** Shared CLI-subcommand adapter. Output parsers are pure def-owned functions, not vendor tools. */
@@ -23,4 +24,13 @@ export declare function parsePiRpcMetadata(stdout: string): ModelProbeResult;
23
24
  export declare function getRuntimeModelDiscoveryDeps({ state }: {
24
25
  state: AgentRuntimeState;
25
26
  }, _optional?: Record<string, never>): ModelDiscoveryDeps;
27
+ /** Adapt the existing ACP port to catalog metadata; array-only transports cannot claim a default.
28
+ * Keeps invocation normalization, cancellation and the shared process budget in the native adapter.
29
+ * @param required - The host's existing ACP probe, optionally capable of reporting current selection.
30
+ * @returns A catalog adapter using that probe without changing security or concurrency guarantees.
31
+ * @example const acp = createAcpDiscoveryAdapter({ probe: hostProbe });
32
+ */
33
+ export declare function createAcpDiscoveryAdapter({ probe }: {
34
+ probe: AcpModelProbe;
35
+ }, _optional?: Record<string, never>): ModelDiscoveryDeps['acp'];
26
36
  //# sourceMappingURL=model-discovery-adapters.d.ts.map
@@ -1,11 +1,10 @@
1
1
  import { defaultRuntimeState } from './runtime-state.js';
2
- import { VENDOR_API_BASE_URLS } from '@jini-ai/integrations/media-providers/catalog';
3
2
  import { execFile } from 'node:child_process';
4
3
  import { readFile, stat } from 'node:fs/promises';
5
4
  import { homedir } from 'node:os';
6
5
  import { join, dirname } from 'node:path';
7
6
  import { createCommandInvocation } from '@jini-ai/platform/command';
8
- import { detectAcpModelMetadata } from './agent-protocol/acp/models.js';
7
+ import { nativeAcpModelProbe } from './acp-model-probe.js';
9
8
  import { CLAUDE_CODE_INITIALIZE_ARGS, CLAUDE_CODE_INITIALIZE_REQUEST } from './claude-code-models.js';
10
9
  import { listProviderModels } from './providers/model-catalog.js';
11
10
  import { execAgentFile } from './invocation.js';
@@ -303,6 +302,21 @@ function runProcess(input) {
303
302
  child.stdin?.end(input.stdin);
304
303
  }) }, { signal: input.signal });
305
304
  }
305
+ /** Load the catalog owner only for a default URL, so importing the runtime never needs this peer.
306
+ * @param required - The vendor whose default endpoint is needed.
307
+ * @returns The catalog-owned base URL; explicit host URLs bypass this lookup.
308
+ * @throws An actionable load error retaining the original import failure as its cause.
309
+ * @complexity O(1) lookup after Node's cached module load.
310
+ */
311
+ async function defaultProviderBaseUrl({ provider }, _optional = {}) {
312
+ try {
313
+ const { VENDOR_API_BASE_URLS } = await import('@jini-ai/integrations/media-providers/catalog');
314
+ return VENDOR_API_BASE_URLS[provider];
315
+ }
316
+ catch (cause) {
317
+ throw new Error('Could not load @jini-ai/integrations/media-providers/catalog. Install the optional peer @jini-ai/integrations or inject ModelDiscoveryDeps.credential to discover models with default provider URLs.', { cause });
318
+ }
319
+ }
306
320
  export const defaultModelDiscoveryDeps = {
307
321
  process: { run: runProcess },
308
322
  fs: {
@@ -326,25 +340,21 @@ export const defaultModelDiscoveryDeps = {
326
340
  // handles. BYOK env is usable only when it identifies this CLI's effective provider.
327
341
  const env = context.env;
328
342
  if (agentId === 'claude' && env.ANTHROPIC_API_KEY)
329
- return { protocol: 'anthropic', baseUrl: env.ANTHROPIC_BASE_URL || VENDOR_API_BASE_URLS.anthropic, apiKey: env.ANTHROPIC_API_KEY };
343
+ return { protocol: 'anthropic', baseUrl: env.ANTHROPIC_BASE_URL || await defaultProviderBaseUrl({ provider: 'anthropic' }), apiKey: env.ANTHROPIC_API_KEY };
330
344
  const selected = context.model || (agentId === 'aider' ? env.AIDER_MODEL : agentId === 'qwen' ? env.QWEN_MODEL || env.OPENAI_MODEL : undefined);
331
345
  if (!selected)
332
346
  return null;
333
347
  if (/^(anthropic\/|claude-)/.test(selected) && env.ANTHROPIC_API_KEY)
334
- return { protocol: 'anthropic', baseUrl: env.ANTHROPIC_BASE_URL || VENDOR_API_BASE_URLS.anthropic, apiKey: env.ANTHROPIC_API_KEY };
348
+ return { protocol: 'anthropic', baseUrl: env.ANTHROPIC_BASE_URL || await defaultProviderBaseUrl({ provider: 'anthropic' }), apiKey: env.ANTHROPIC_API_KEY };
335
349
  if (/^(gemini\/|google\/|gemini-)/.test(selected) && env.GEMINI_API_KEY)
336
- return { protocol: 'google', baseUrl: VENDOR_API_BASE_URLS.google, apiKey: env.GEMINI_API_KEY };
350
+ return { protocol: 'google', baseUrl: await defaultProviderBaseUrl({ provider: 'google' }), apiKey: env.GEMINI_API_KEY };
337
351
  if (/^openrouter\//.test(selected) && env.OPENROUTER_API_KEY)
338
- return { protocol: 'openai', baseUrl: env.OPENROUTER_API_BASE || VENDOR_API_BASE_URLS.openrouter, apiKey: env.OPENROUTER_API_KEY };
352
+ return { protocol: 'openai', baseUrl: env.OPENROUTER_API_BASE || await defaultProviderBaseUrl({ provider: 'openrouter' }), apiKey: env.OPENROUTER_API_KEY };
339
353
  if ((/^(openai\/|gpt-|o\d)/.test(selected) || agentId === 'qwen' && (env.OPENAI_MODEL || env.OPENAI_BASE_URL)) && env.OPENAI_API_KEY)
340
- return { protocol: 'openai', baseUrl: env.OPENAI_BASE_URL || env.OPENAI_API_BASE || VENDOR_API_BASE_URLS.openai, apiKey: env.OPENAI_API_KEY };
354
+ return { protocol: 'openai', baseUrl: env.OPENAI_BASE_URL || env.OPENAI_API_BASE || await defaultProviderBaseUrl({ provider: 'openai' }), apiKey: env.OPENAI_API_KEY };
341
355
  return null;
342
356
  } },
343
- acp: { async probe({ context, args, signal }) {
344
- const invocation = createCommandInvocation({ command: context.executable, args: [...args], env: context.env });
345
- const result = await scheduleProbeProcess({ work: () => detectAcpModelMetadata({ bin: invocation.command, args: invocation.args }, { cwd: context.cwd, env: context.env, timeoutMs: 15_000, signal }) }, { signal });
346
- return { models: result.models, source: 'rpc', coverage: 'account', ...(result.currentModelId ? { defaultSelectionId: result.currentModelId } : {}) };
347
- } },
357
+ acp: createAcpDiscoveryAdapter({ probe: nativeAcpModelProbe }),
348
358
  rpc: { async probe({ agentId, context, signal }) {
349
359
  // UNVERIFIED locally: use official prompt-free SDK interfaces, not invented CLI flags.
350
360
  if (agentId === 'copilot')
@@ -399,6 +409,27 @@ export function parsePiRpcMetadata(stdout) {
399
409
  export function getRuntimeModelDiscoveryDeps({ state }, _optional = {}) {
400
410
  if (state === defaultRuntimeState)
401
411
  return defaultModelDiscoveryDeps;
402
- return state.discoveryDeps ??= { ...defaultModelDiscoveryDeps, cache: state.modelCatalog };
412
+ return state.discoveryDeps ??= { ...defaultModelDiscoveryDeps, cache: state.modelCatalog,
413
+ acp: createAcpDiscoveryAdapter({ probe: state.acpModelProbe ?? nativeAcpModelProbe }),
414
+ };
415
+ }
416
+ /** Adapt the existing ACP port to catalog metadata; array-only transports cannot claim a default.
417
+ * Keeps invocation normalization, cancellation and the shared process budget in the native adapter.
418
+ * @param required - The host's existing ACP probe, optionally capable of reporting current selection.
419
+ * @returns A catalog adapter using that probe without changing security or concurrency guarantees.
420
+ * @example const acp = createAcpDiscoveryAdapter({ probe: hostProbe });
421
+ */
422
+ export function createAcpDiscoveryAdapter({ probe }, _optional = {}) {
423
+ return { async probe({ context, args, signal }) {
424
+ const invocation = createCommandInvocation({ command: context.executable, args: [...args], env: context.env });
425
+ const required = { bin: invocation.command, args: invocation.args };
426
+ const optional = { cwd: context.cwd, env: context.env, timeoutMs: 15_000, signal };
427
+ const result = await scheduleProbeProcess({ work: async () => {
428
+ if (probe.detectMetadata)
429
+ return probe.detectMetadata(required, optional);
430
+ return { models: await probe.detectModels(required, optional), currentModelId: null };
431
+ } }, { signal });
432
+ return { models: result.models, source: 'rpc', coverage: 'account', ...(result.currentModelId ? { defaultSelectionId: result.currentModelId } : {}) };
433
+ } };
403
434
  }
404
435
  //# sourceMappingURL=model-discovery-adapters.js.map
@@ -0,0 +1,35 @@
1
+ /** Native host composition: one state and transport, with environment policies resolved once. */
2
+ import { type AcpModelProbe } from './acp-model-probe.js';
3
+ import { detectAgents, probeAgentModels } from './detection.js';
4
+ import { resolveAgentLaunch } from './launch.js';
5
+ import { getAgentDef } from './registry.js';
6
+ import { type AgentRuntimeState } from './runtime-state.js';
7
+ import type { RuntimeEnv } from './types.js';
8
+ export interface NativeAgentRuntime {
9
+ readonly state: AgentRuntimeState;
10
+ readonly hostEnv: RuntimeEnv;
11
+ readonly codexDisablePlugins: boolean;
12
+ readonly piGracefulShutdownMs: number;
13
+ readonly detectAgents: typeof detectAgents;
14
+ readonly probeAgentModels: typeof probeAgentModels;
15
+ readonly resolveAgentLaunch: typeof resolveAgentLaunch;
16
+ readonly getAgentDef: (required: Parameters<typeof getAgentDef>[0], optional?: Record<string, never>) => ReturnType<typeof getAgentDef>;
17
+ /** Drain owned discovery before clearing caches, so late probes cannot repopulate them. */
18
+ dispose(required: Record<string, never>, optional?: Record<string, never>): Promise<void>;
19
+ }
20
+ /** Bind existing runtime owners to a host; copies env without performing I/O.
21
+ * @param required - The host's selected environment, used instead of ambient process configuration.
22
+ * @param optional - An existing state, ACP transport, or explicit policies overriding env defaults.
23
+ * @returns Bound runtime operations and an idempotent cache disposer. Stop runs before disposing.
24
+ * Discovery after disposal rejects with `InvalidStateError`; native probe failures retain their existing outcomes.
25
+ * @example const runtime = createNativeAgentRuntime({ env: process.env });
26
+ */
27
+ export declare function createNativeAgentRuntime({ env }: {
28
+ env: RuntimeEnv;
29
+ }, { state, acpModelProbe, codexDisablePlugins, piGracefulShutdownMs, }?: {
30
+ state?: AgentRuntimeState;
31
+ acpModelProbe?: AcpModelProbe;
32
+ codexDisablePlugins?: boolean;
33
+ piGracefulShutdownMs?: number;
34
+ }): NativeAgentRuntime;
35
+ //# sourceMappingURL=native-runtime.d.ts.map
@@ -0,0 +1,53 @@
1
+ /** Native host composition: one state and transport, with environment policies resolved once. */
2
+ import { nativeAcpModelProbe } from './acp-model-probe.js';
3
+ import { detectAgents, probeAgentModels } from './detection.js';
4
+ import { resolveAgentLaunch } from './launch.js';
5
+ import { getAgentDef } from './registry.js';
6
+ import { createAcpDiscoveryAdapter } from './model-discovery-adapters.js';
7
+ import { bindRuntimeAgentDef, createAgentRuntimeState } from './runtime-state.js';
8
+ /** Bind existing runtime owners to a host; copies env without performing I/O.
9
+ * @param required - The host's selected environment, used instead of ambient process configuration.
10
+ * @param optional - An existing state, ACP transport, or explicit policies overriding env defaults.
11
+ * @returns Bound runtime operations and an idempotent cache disposer. Stop runs before disposing.
12
+ * Discovery after disposal rejects with `InvalidStateError`; native probe failures retain their existing outcomes.
13
+ * @example const runtime = createNativeAgentRuntime({ env: process.env });
14
+ */
15
+ export function createNativeAgentRuntime({ env }, { state = createAgentRuntimeState({}), acpModelProbe = state.acpModelProbe ?? nativeAcpModelProbe, codexDisablePlugins = env.CODEX_DISABLE_PLUGINS === '1', piGracefulShutdownMs = Number(env.PI_GRACEFUL_SHUTDOWN_MS) || 5000, } = {}) {
16
+ const hostEnv = { ...env };
17
+ if (state.discoveryDeps && state.acpModelProbe !== acpModelProbe) {
18
+ state.discoveryDeps = { ...state.discoveryDeps, acp: createAcpDiscoveryAdapter({ probe: acpModelProbe }) };
19
+ }
20
+ state.acpModelProbe = acpModelProbe;
21
+ const pending = new Set();
22
+ let disposal;
23
+ /** Retain operations until disposal can wait for their final cache writes. */
24
+ function track(work) {
25
+ if (disposal)
26
+ return Promise.reject(new DOMException('Agent runtime is disposed', 'InvalidStateError'));
27
+ const result = work();
28
+ pending.add(result);
29
+ void result.then(() => pending.delete(result), () => pending.delete(result));
30
+ return result;
31
+ }
32
+ return {
33
+ state, hostEnv, codexDisablePlugins, piGracefulShutdownMs,
34
+ detectAgents: (required, optional = {}) => track(() => detectAgents(required, { ...optional, state, env: hostEnv })),
35
+ probeAgentModels: (required, optional = {}) => track(() => probeAgentModels(required, { ...optional, state, env: hostEnv })),
36
+ resolveAgentLaunch: (required, optional = {}) => resolveAgentLaunch(required, { ...optional, state, env: hostEnv }),
37
+ getAgentDef: (required, _optional = {}) => {
38
+ const def = getAgentDef(required);
39
+ return def ? bindRuntimeAgentDef({ def, state }, { codexDisablePlugins }) : def;
40
+ },
41
+ dispose(_required, _optional = {}) {
42
+ disposal ??= (async () => {
43
+ await Promise.allSettled([...pending, ...state.detections.values(), ...state.capabilityProbes.values(),
44
+ ...[...state.modelCatalog.values()].flatMap(entry => entry.pending ? [entry.pending] : []),
45
+ ...[...state.claudeModels.values()].map(entry => entry.value),
46
+ ]);
47
+ state.clear({}, {});
48
+ })();
49
+ return disposal;
50
+ },
51
+ };
52
+ }
53
+ //# sourceMappingURL=native-runtime.js.map
@@ -1,3 +1,4 @@
1
+ import { type Clock, type HttpClientPort } from '@jini-ai/core/primitives';
1
2
  export interface ElevenLabsVoiceOption {
2
3
  voiceId: string;
3
4
  name: string;
@@ -17,16 +18,32 @@ export type ElevenLabsCredentialResolver = (requiredArgs: {
17
18
  export declare class ElevenLabsCredentialMissingError extends Error {
18
19
  constructor();
19
20
  }
20
- /**
21
- * Lists the ElevenLabs voice catalog for a workspace, resolving credentials
22
- * via the injected {@link ElevenLabsCredentialResolver} and caching the
23
- * result for {@link ELEVENLABS_VOICE_CACHE_TTL_MS}. Throws
24
- * {@link ElevenLabsCredentialMissingError} when no API key is configured.
25
- */
26
- export declare function listElevenLabsVoiceOptions({ workspaceKey, resolveCredentials }: {
21
+ export interface ElevenLabsVoiceClientOptions {
22
+ /** Maximum cached workspace/credential/page variants; oldest inserted entries are evicted. */
23
+ maxCacheEntries?: number;
24
+ }
25
+ export interface ElevenLabsVoiceListRequired {
27
26
  workspaceKey: string;
28
27
  resolveCredentials: ElevenLabsCredentialResolver;
29
- }, options?: {
28
+ }
29
+ export interface ElevenLabsVoiceListOptions {
30
+ limit?: number;
31
+ }
32
+ /** Owns transport, time and a bounded voice cache for one host lifecycle.
33
+ * Supply the existing policy-enforcing HTTP client; this factory never constructs a raw transport.
34
+ * @throws RangeError for an invalid cache bound; credential/transport/JSON errors propagate from listing.
35
+ * @complexity O(c + v) per lookup, c <= maxCacheEntries and v <= the requested page size.
36
+ */
37
+ export declare function createElevenLabsVoiceClient({ http, clock }: {
38
+ readonly http: HttpClientPort;
39
+ readonly clock: Clock;
40
+ }, { maxCacheEntries }?: ElevenLabsVoiceClientOptions): {
41
+ listVoiceOptions: ({ workspaceKey, resolveCredentials }: ElevenLabsVoiceListRequired, options?: ElevenLabsVoiceListOptions) => Promise<ElevenLabsVoiceOption[]>;
42
+ clearCache: () => void;
43
+ };
44
+ export type ElevenLabsVoiceClient = ReturnType<typeof createElevenLabsVoiceClient>;
45
+ /** Compatibility wrapper for existing callers. New hosts retain an injected client and clear it on shutdown. */
46
+ export declare function listElevenLabsVoiceOptions(required: ElevenLabsVoiceListRequired, options?: {
30
47
  limit?: number;
31
48
  requestInit?: Pick<RequestInit, 'dispatcher'>;
32
49
  }): Promise<ElevenLabsVoiceOption[]>;
@@ -2,7 +2,7 @@
2
2
  * @module providers/elevenlabs
3
3
  *
4
4
  * ElevenLabs voice-catalog listing (`GET /v2/voices`), with a short in-memory
5
- * TTL cache keyed by workspace + base URL + page size + a credential
5
+ * bounded, client-owned TTL cache keyed by workspace + base URL + page size + a credential
6
6
  * fingerprint (never the raw key). Ported from OD's
7
7
  * `integrations/elevenlabs-voices.ts`.
8
8
  *
@@ -16,6 +16,7 @@
16
16
  * ElevenLabs wire API once it has a key.
17
17
  */
18
18
  import { createHash } from 'node:crypto';
19
+ import { createSystemClock } from '@jini-ai/core/primitives';
19
20
  import { FETCH_TIMEOUT_MS, fetchWithTimeout } from '@jini-ai/platform/fetch-with-timeout';
20
21
  const ELEVENLABS_DEFAULT_BASE_URL = 'https://api.elevenlabs.io';
21
22
  const ELEVENLABS_DEFAULT_VOICE_LIMIT = 100;
@@ -28,7 +29,6 @@ export class ElevenLabsCredentialMissingError extends Error {
28
29
  this.name = 'ElevenLabsCredentialMissingError';
29
30
  }
30
31
  }
31
- const voiceOptionsCache = new Map();
32
32
  function isRecord(value) {
33
33
  return value !== null && typeof value === 'object';
34
34
  }
@@ -87,53 +87,90 @@ function cloneVoiceOptions(voices) {
87
87
  ...(voice.labels ? { labels: { ...voice.labels } } : {}),
88
88
  }));
89
89
  }
90
- /**
91
- * Lists the ElevenLabs voice catalog for a workspace, resolving credentials
92
- * via the injected {@link ElevenLabsCredentialResolver} and caching the
93
- * result for {@link ELEVENLABS_VOICE_CACHE_TTL_MS}. Throws
94
- * {@link ElevenLabsCredentialMissingError} when no API key is configured.
90
+ /** Owns transport, time and a bounded voice cache for one host lifecycle.
91
+ * Supply the existing policy-enforcing HTTP client; this factory never constructs a raw transport.
92
+ * @throws RangeError for an invalid cache bound; credential/transport/JSON errors propagate from listing.
93
+ * @complexity O(c + v) per lookup, c <= maxCacheEntries and v <= the requested page size.
95
94
  */
96
- export async function listElevenLabsVoiceOptions({ workspaceKey, resolveCredentials }, options = {}) {
97
- const credentials = await resolveCredentials({ workspaceKey });
98
- if (!credentials.apiKey) {
99
- throw new ElevenLabsCredentialMissingError();
95
+ export function createElevenLabsVoiceClient({ http, clock }, { maxCacheEntries = 64 } = {}) {
96
+ if (!Number.isSafeInteger(maxCacheEntries) || maxCacheEntries < 1) {
97
+ throw new RangeError('maxCacheEntries must be a positive safe integer');
98
+ }
99
+ const cache = new Map();
100
+ function sweepExpired(now) {
101
+ for (const [key, entry] of cache)
102
+ if (entry.expiresAt <= now)
103
+ cache.delete(key);
100
104
  }
101
- const baseUrl = (credentials.baseUrl || ELEVENLABS_DEFAULT_BASE_URL).replace(/\/$/, '');
102
- const pageSize = clampLimit(options.limit);
103
- const cacheKey = voiceCacheKey({
104
- workspaceKey,
105
- baseUrl,
106
- apiKey: credentials.apiKey,
107
- pageSize,
108
- });
109
- const cached = voiceOptionsCache.get(cacheKey);
110
- const now = Date.now();
111
- if (cached && cached.expiresAt > now) {
112
- return cloneVoiceOptions(cached.voices);
105
+ /** Drops retained voices so a host can release them when its lifecycle ends. */
106
+ function clearCache() { cache.clear(); }
107
+ /** Lists and clones credential-scoped catalog values; unsuccessful responses are never cached. */
108
+ async function listVoiceOptions({ workspaceKey, resolveCredentials }, options = {}) {
109
+ const credentials = await resolveCredentials({ workspaceKey });
110
+ if (!credentials.apiKey)
111
+ throw new ElevenLabsCredentialMissingError();
112
+ const baseUrl = (credentials.baseUrl || ELEVENLABS_DEFAULT_BASE_URL).replace(/\/$/, '');
113
+ const pageSize = clampLimit(options.limit);
114
+ const cacheKey = voiceCacheKey({ workspaceKey, baseUrl, apiKey: credentials.apiKey, pageSize });
115
+ sweepExpired(clock.nowMs());
116
+ const cached = cache.get(cacheKey);
117
+ if (cached)
118
+ return cloneVoiceOptions(cached.voices);
119
+ const resp = await http.send({ request: {
120
+ url: `${baseUrl}/v2/voices?page_size=${pageSize}`,
121
+ method: 'GET',
122
+ headers: { 'xi-api-key': credentials.apiKey, accept: 'application/json' },
123
+ idleTimeoutMs: FETCH_TIMEOUT_MS.QUICK,
124
+ totalDeadlineMs: FETCH_TIMEOUT_MS.QUICK,
125
+ maxResponseBytes: 1024 * 1024,
126
+ } });
127
+ if (resp.status < 200 || resp.status >= 300) {
128
+ throw new Error(`elevenlabs voices ${resp.status}: ${resp.bodyText.slice(0, 240)}`);
129
+ }
130
+ if (resp.bodyTruncated)
131
+ throw new Error('elevenlabs voices response was truncated');
132
+ const payload = JSON.parse(resp.bodyText);
133
+ const rawVoices = isRecord(payload) && Array.isArray(payload.voices) ? payload.voices : [];
134
+ const voices = rawVoices.map((voice) => normalizeVoice(voice))
135
+ .filter((voice) => voice !== null);
136
+ const now = clock.nowMs();
137
+ sweepExpired(now);
138
+ // Replacing a key refreshes insertion order; concurrent misses cannot grow past the bound.
139
+ cache.delete(cacheKey);
140
+ if (cache.size >= maxCacheEntries)
141
+ cache.delete(cache.keys().next().value);
142
+ cache.set(cacheKey, { expiresAt: now + ELEVENLABS_VOICE_CACHE_TTL_MS, voices: cloneVoiceOptions(voices) });
143
+ return voices;
113
144
  }
114
- const resp = await fetchWithTimeout(`${baseUrl}/v2/voices?page_size=${pageSize}`, {
115
- ...options.requestInit,
116
- method: 'GET',
117
- headers: {
118
- 'xi-api-key': credentials.apiKey,
119
- accept: 'application/json',
120
- },
121
- }, { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
122
- if (!resp.ok) {
123
- const errText = await resp.text();
124
- throw new Error(`elevenlabs voices ${resp.status}: ${errText.slice(0, 240)}`);
145
+ return { listVoiceOptions, clearCache };
146
+ }
147
+ /** Native defaults stay in this compatibility assembly; injected clients use HttpClientPort directly. */
148
+ function createCompatibilityClient(requestInit = {}) {
149
+ return createElevenLabsVoiceClient({ clock: createSystemClock(), http: {
150
+ async send({ request }) {
151
+ const response = await fetchWithTimeout(request.url, {
152
+ ...requestInit, method: request.method, headers: request.headers,
153
+ }, { timeoutMs: FETCH_TIMEOUT_MS.QUICK });
154
+ // Same reads as before the port: json() on success, text() for the error message.
155
+ return { status: response.status, headers: {},
156
+ bodyText: response.ok ? JSON.stringify(await response.json()) : await response.text() };
157
+ },
158
+ } });
159
+ }
160
+ const compatibilityClient = createCompatibilityClient();
161
+ const dispatcherClients = new WeakMap();
162
+ /** Compatibility wrapper for existing callers. New hosts retain an injected client and clear it on shutdown. */
163
+ export async function listElevenLabsVoiceOptions(required, options = {}) {
164
+ let client = compatibilityClient;
165
+ const dispatcher = options.requestInit?.dispatcher;
166
+ if (dispatcher && typeof dispatcher === 'object') {
167
+ let owned = dispatcherClients.get(dispatcher);
168
+ if (!owned) {
169
+ owned = createCompatibilityClient(options.requestInit);
170
+ dispatcherClients.set(dispatcher, owned);
171
+ }
172
+ client = owned;
125
173
  }
126
- const payload = await resp.json();
127
- const rawVoices = isRecord(payload) && Array.isArray(payload.voices)
128
- ? payload.voices
129
- : [];
130
- const voices = rawVoices
131
- .map((voice) => normalizeVoice(voice))
132
- .filter((voice) => voice !== null);
133
- voiceOptionsCache.set(cacheKey, {
134
- expiresAt: now + ELEVENLABS_VOICE_CACHE_TTL_MS,
135
- voices: cloneVoiceOptions(voices),
136
- });
137
- return voices;
174
+ return client.listVoiceOptions(required, options);
138
175
  }
139
176
  //# sourceMappingURL=elevenlabs.js.map
@@ -1,4 +1,5 @@
1
1
  /** Mutable runtime data belongs to one host. Compatibility functions use defaultRuntimeState. */
2
+ import type { AcpModelProbe } from './acp-model-probe.js';
2
3
  import type { RuntimeAgentDef, RuntimeCapabilityMap, RuntimeModelOption, DetectedAgent } from './types.js';
3
4
  import type { ModelCacheEntry, ModelDiscoveryDeps } from './model-discovery-types.js';
4
5
  export interface AgentRuntimeState {
@@ -13,6 +14,8 @@ export interface AgentRuntimeState {
13
14
  expiresAt: number;
14
15
  value: Promise<readonly RuntimeModelOption[] | null>;
15
16
  }>;
17
+ /** Host-owned transport. Absent only for legacy definitions using the compatibility wrapper. */
18
+ acpModelProbe?: AcpModelProbe;
16
19
  discoveryDeps?: ModelDiscoveryDeps;
17
20
  readonly modelCatalog: Map<string, ModelCacheEntry>;
18
21
  readonly executable: {
@@ -32,10 +35,12 @@ export interface AgentRuntimeState {
32
35
  * Returns empty owned maps; no I/O. @complexity O(1) initialization; retained data grows with probes. */
33
36
  export declare function createAgentRuntimeState(_required: Record<string, never>, _optional?: Record<string, never>): AgentRuntimeState;
34
37
  export declare const defaultRuntimeState: AgentRuntimeState;
35
- /** Bind the agent argument builder to the same capability state detection and launch use.
38
+ /** Bind model probes and the argument builder to the host transport, capability state and plugin policy.
36
39
  * Returns a definition copy without mutating the registry. @complexity O(1). */
37
40
  export declare function bindRuntimeAgentDef({ def, state }: {
38
41
  def: RuntimeAgentDef;
39
42
  state: AgentRuntimeState;
40
- }, _optional?: Record<string, never>): RuntimeAgentDef;
43
+ }, { codexDisablePlugins }?: {
44
+ codexDisablePlugins?: boolean;
45
+ }): RuntimeAgentDef;
41
46
  //# sourceMappingURL=runtime-state.d.ts.map
@@ -21,11 +21,17 @@ export function createAgentRuntimeState(_required, _optional = {}) {
21
21
  return state;
22
22
  }
23
23
  export const defaultRuntimeState = createAgentRuntimeState({});
24
- /** Bind the agent argument builder to the same capability state detection and launch use.
24
+ /** Bind model probes and the argument builder to the host transport, capability state and plugin policy.
25
25
  * Returns a definition copy without mutating the registry. @complexity O(1). */
26
- export function bindRuntimeAgentDef({ def, state }, _optional = {}) {
27
- return { ...def, buildArgs: (required, optional = {}) => def.buildArgs(required, {
28
- ...optional, options: { ...optional.options, capabilities: state.capabilities.get(def.id) ?? {} },
26
+ export function bindRuntimeAgentDef({ def, state }, { codexDisablePlugins } = {}) {
27
+ const fetchModels = def.fetchModels;
28
+ const boundFetchModels = fetchModels
29
+ ? (required, optional = {}) => fetchModels(required, {
30
+ ...optional, ...(state.acpModelProbe ? { acpModelProbe: state.acpModelProbe } : {}),
31
+ }) : undefined;
32
+ return { ...def, ...(boundFetchModels ? { fetchModels: boundFetchModels } : {}), buildArgs: (required, optional = {}) => def.buildArgs(required, {
33
+ ...optional, options: { ...optional.options, capabilities: state.capabilities.get(def.id) ?? {},
34
+ ...(codexDisablePlugins !== undefined ? { codexDisablePlugins } : {}) },
29
35
  }) };
30
36
  }
31
37
  //# sourceMappingURL=runtime-state.js.map
package/dist/types.d.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  * Ported from OD's `apps/daemon/src/runtimes/types.ts`. Product-neutral
12
12
  * as found — see `archived provenance ledger` for the full provenance table.
13
13
  */
14
+ import type { AcpModelProbe } from './acp-model-probe.js';
14
15
  import type { AgentModelDiscoveryPort, ModelCatalogSnapshot, DefaultModelResolution, ModelIdentityKind } from './model-discovery-types.js';
15
16
  import type { ExecFileOptions } from 'node:child_process';
16
17
  import type { AgentDiagnostic } from '@jini-ai/protocol';
@@ -75,6 +76,8 @@ export type RuntimeReasoningInModelId = {
75
76
  levels: readonly RuntimeReasoningOption[];
76
77
  };
77
78
  export type RuntimeBuildOptions = {
79
+ /** Explicit host/run policy; environment defaults belong to native composition. */
80
+ codexDisablePlugins?: boolean;
78
81
  /** Host-scoped capability flags; absent retains the published compatibility cache. */
79
82
  capabilities?: RuntimeCapabilityMap;
80
83
  model?: string | null;
@@ -308,6 +311,8 @@ export type RuntimeAgentDef = Partial<AgentModelDiscoveryPort> & {
308
311
  fetchModels?: (requiredArgs: {
309
312
  resolvedBin: string;
310
313
  env: RuntimeEnv;
314
+ }, optionalArgs?: {
315
+ acpModelProbe?: AcpModelProbe;
311
316
  }) => Promise<RuntimeModelOption[] | null>;
312
317
  reasoningOptions?: RuntimeReasoningOption[];
313
318
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/agent-runtime",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "Coding-agent runtime adapters: runtime registry, detection, launch, stream parsers, and the ACP/pi-rpc subprocess transport.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -101,8 +101,8 @@
101
101
  "@jini-ai/core": "^0.4.0",
102
102
  "@jini-ai/integrations": "^0.4.2",
103
103
  "@jini-ai/platform": "^0.5.5",
104
- "@jini-ai/protocol": "^0.4.4",
105
- "@jini-ai/oauth": "^0.2.2"
104
+ "@jini-ai/oauth": "^0.2.2",
105
+ "@jini-ai/protocol": "^0.4.4"
106
106
  },
107
107
  "dependencies": {
108
108
  "@jini-ai/core": "^0.4.4"