comisai 1.0.23 → 1.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/node_modules/@comis/agent/dist/executor/pi-executor.js +17 -0
  2. package/node_modules/@comis/agent/dist/index.d.ts +2 -1
  3. package/node_modules/@comis/agent/dist/index.js +1 -1
  4. package/node_modules/@comis/agent/dist/model/auth-storage-adapter.d.ts +21 -0
  5. package/node_modules/@comis/agent/dist/model/auth-storage-adapter.js +15 -1
  6. package/node_modules/@comis/agent/dist/model/model-registry-adapter.d.ts +46 -0
  7. package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +108 -0
  8. package/node_modules/@comis/agent/package.json +1 -1
  9. package/node_modules/@comis/channels/package.json +1 -1
  10. package/node_modules/@comis/cli/package.json +1 -1
  11. package/node_modules/@comis/core/dist/bootstrap.js +5 -0
  12. package/node_modules/@comis/core/dist/config/env-layer.d.ts +31 -0
  13. package/node_modules/@comis/core/dist/config/env-layer.js +41 -0
  14. package/node_modules/@comis/core/dist/config/layered.d.ts +9 -0
  15. package/node_modules/@comis/core/dist/config/layered.js +11 -0
  16. package/node_modules/@comis/core/package.json +1 -1
  17. package/node_modules/@comis/daemon/dist/daemon.js +3 -0
  18. package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +15 -3
  19. package/node_modules/@comis/daemon/package.json +1 -1
  20. package/node_modules/@comis/gateway/package.json +1 -1
  21. package/node_modules/@comis/infra/package.json +1 -1
  22. package/node_modules/@comis/memory/package.json +1 -1
  23. package/node_modules/@comis/scheduler/package.json +1 -1
  24. package/node_modules/@comis/shared/package.json +1 -1
  25. package/node_modules/@comis/skills/package.json +1 -1
  26. package/node_modules/@comis/web/dist/assets/{agent-detail-BG9MGWWj.js → agent-detail-ru-AhppM.js} +270 -270
  27. package/node_modules/@comis/web/dist/assets/agent-editor-hjwRuFVp.js +2173 -0
  28. package/node_modules/@comis/web/dist/assets/{agent-list-LHCJ4rw2.js → agent-list-6Uotjatr.js} +170 -170
  29. package/node_modules/@comis/web/dist/assets/{approvals-q9VH_IKr.js → approvals-C-K6hN2U.js} +13 -13
  30. package/node_modules/@comis/web/dist/assets/billing-view-CxysXH0p.js +375 -0
  31. package/node_modules/@comis/web/dist/assets/{channel-detail-CaInesJM.js → channel-detail-BBCKtmne.js} +265 -265
  32. package/node_modules/@comis/web/dist/assets/channel-list-FkfeOLBQ.js +323 -0
  33. package/node_modules/@comis/web/dist/assets/{chat-console-CNmzl0JW.js → chat-console-BumBaIgO.js} +243 -246
  34. package/node_modules/@comis/web/dist/assets/{config-editor-DX4ITw6y.js → config-editor-C9BSwHGy.js} +477 -477
  35. package/node_modules/@comis/web/dist/assets/{context-dag-browser-BwiaF5tf.js → context-dag-browser-BHm00mJD.js} +105 -105
  36. package/node_modules/@comis/web/dist/assets/{context-engine-BZ5Am6hA.js → context-engine-BENY3pWE.js} +136 -136
  37. package/node_modules/@comis/web/dist/assets/decorate-BvWYovGE.js +38 -0
  38. package/node_modules/@comis/web/dist/assets/{delivery-view-OfBZof-m.js → delivery-view-BCnkPsAp.js} +134 -134
  39. package/node_modules/@comis/web/dist/assets/{diagnostics-view-YFwCxgr2.js → diagnostics-view-C_jQFG2H.js} +82 -82
  40. package/node_modules/@comis/web/dist/assets/directive-BOYXJ-K-.js +1 -0
  41. package/node_modules/@comis/web/dist/assets/{extract-variables-BM5qyK-s.js → extract-variables-B7-Doo7l.js} +39 -39
  42. package/node_modules/@comis/web/dist/assets/{ic-array-editor-B7m6x7-S.js → ic-array-editor-BLoEyeLS.js} +29 -29
  43. package/node_modules/@comis/web/dist/assets/{ic-breadcrumb-CUMpp3BL.js → ic-breadcrumb-DqN6G3gc.js} +16 -16
  44. package/node_modules/@comis/web/dist/assets/{ic-budget-segment-bar-BtJ6x5mN.js → ic-budget-segment-bar-zLsMzjDO.js} +20 -20
  45. package/node_modules/@comis/web/dist/assets/ic-chat-message-FdQcZsSQ.js +352 -0
  46. package/node_modules/@comis/web/dist/assets/{ic-confirm-dialog-CCDbB04e.js → ic-confirm-dialog-DGlPbV1T.js} +26 -26
  47. package/node_modules/@comis/web/dist/assets/{ic-connection-dot-CnT1b8xr.js → ic-connection-dot-BgYiK2N4.js} +13 -13
  48. package/node_modules/@comis/web/dist/assets/ic-data-table-CKIvr-ag.js +277 -0
  49. package/node_modules/@comis/web/dist/assets/ic-delivery-row-B3YwjjuM.js +67 -0
  50. package/node_modules/@comis/web/dist/assets/{ic-detail-panel-BF83r-if.js → ic-detail-panel-DiCe4hLr.js} +27 -27
  51. package/node_modules/@comis/web/dist/assets/{ic-empty-state-60l2ePhd.js → ic-empty-state-CM3Wbj2f.js} +19 -19
  52. package/node_modules/@comis/web/dist/assets/ic-graph-canvas-ByRjij68.js +359 -0
  53. package/node_modules/@comis/web/dist/assets/ic-icon-BGNCCPpZ.js +33 -0
  54. package/node_modules/@comis/web/dist/assets/{ic-layer-waterfall-COvEYMg5.js → ic-layer-waterfall-WkaFyu-l.js} +18 -18
  55. package/node_modules/@comis/web/dist/assets/ic-relative-time-B3UAnTqg.js +12 -0
  56. package/node_modules/@comis/web/dist/assets/{ic-search-input-CSOxY9g7.js → ic-search-input-B02AGw1i.js} +22 -22
  57. package/node_modules/@comis/web/dist/assets/{ic-select-Ce-Raudx.js → ic-select-BqfZISjw.js} +29 -29
  58. package/node_modules/@comis/web/dist/assets/ic-tabs-yBjkWKJH.js +95 -0
  59. package/node_modules/@comis/web/dist/assets/ic-tag-CvMVQFRR.js +33 -0
  60. package/node_modules/@comis/web/dist/assets/{ic-time-range-picker-CypCT68y.js → ic-time-range-picker-DXbYeBmY.js} +31 -31
  61. package/node_modules/@comis/web/dist/assets/{ic-tool-call-7MaXSsCW.js → ic-tool-call-DMPHsLyx.js} +51 -51
  62. package/node_modules/@comis/web/dist/assets/index-CVEaS9aY.css +2 -0
  63. package/node_modules/@comis/web/dist/assets/index-FLPhHz8p.js +2792 -0
  64. package/node_modules/@comis/web/dist/assets/{mcp-management-BNZPnpDn.js → mcp-management-5jyScQis.js} +209 -209
  65. package/node_modules/@comis/web/dist/assets/{media-config-BBvTYxOX.js → media-config-J9oT9PPs.js} +154 -154
  66. package/node_modules/@comis/web/dist/assets/{media-test-BkK3RCRK.js → media-test-DGTCtM8-.js} +259 -259
  67. package/node_modules/@comis/web/dist/assets/{memory-inspector-1hDGCGat.js → memory-inspector-D5Re9ptG.js} +450 -450
  68. package/node_modules/@comis/web/dist/assets/{message-center-CXefwsUu.js → message-center-cRLK6ZmG.js} +290 -290
  69. package/node_modules/@comis/web/dist/assets/{models-C1qcU_j3.js → models-D5vu07MR.js} +371 -371
  70. package/node_modules/@comis/web/dist/assets/observability-types-D0tkwElU.js +1 -0
  71. package/node_modules/@comis/web/dist/assets/{observe-view-C0VBhX4C.js → observe-view-CalNNEmd.js} +399 -399
  72. package/node_modules/@comis/web/dist/assets/pipeline-builder-DUYDGwZf.js +1495 -0
  73. package/node_modules/@comis/web/dist/assets/{pipeline-history-DkfOQ6SW.js → pipeline-history-BAO8brOe.js} +124 -124
  74. package/node_modules/@comis/web/dist/assets/{pipeline-history-detail-hyHgD0ai.js → pipeline-history-detail-DectIoQt.js} +65 -65
  75. package/node_modules/@comis/web/dist/assets/{pipeline-list-BPW8hV-q.js → pipeline-list-BHlaBKww.js} +227 -227
  76. package/node_modules/@comis/web/dist/assets/{pipeline-monitor-Bip16T7e.js → pipeline-monitor-BhtpNEHf.js} +298 -298
  77. package/node_modules/@comis/web/dist/assets/{scheduler-BGgwKd06.js → scheduler-VafN_8xi.js} +486 -486
  78. package/node_modules/@comis/web/dist/assets/{security-D15st4xx.js → security-QQXMRTlo.js} +389 -389
  79. package/node_modules/@comis/web/dist/assets/{session-detail-SGEYNJ0M.js → session-detail-BpZ_8Yih.js} +294 -294
  80. package/node_modules/@comis/web/dist/assets/session-key-parser-Dkqcj2Ss.js +1 -0
  81. package/node_modules/@comis/web/dist/assets/session-list-DfCm8Cec.js +231 -0
  82. package/node_modules/@comis/web/dist/assets/{setup-wizard-nT0tz9QP.js → setup-wizard-C-z477CG.js} +486 -494
  83. package/node_modules/@comis/web/dist/assets/{skills-D8yVfSUy.js → skills-BCOGPf6s.js} +329 -329
  84. package/node_modules/@comis/web/dist/assets/{subagents-HHXMeHYo.js → subagents-l-auUraL.js} +74 -74
  85. package/node_modules/@comis/web/dist/assets/{workspace-manager-BQlr10iH.js → workspace-manager-DlvBixiq.js} +236 -236
  86. package/node_modules/@comis/web/dist/index.html +3 -2
  87. package/node_modules/@comis/web/package.json +1 -1
  88. package/package.json +15 -15
  89. package/node_modules/@comis/web/dist/assets/agent-editor-C26Q_xCs.js +0 -2173
  90. package/node_modules/@comis/web/dist/assets/billing-view-CtYvBqTE.js +0 -375
  91. package/node_modules/@comis/web/dist/assets/channel-list-B8dj3O9a.js +0 -323
  92. package/node_modules/@comis/web/dist/assets/directive-DoeGSK_T.js +0 -1
  93. package/node_modules/@comis/web/dist/assets/ic-chat-message-CFyDJd0z.js +0 -352
  94. package/node_modules/@comis/web/dist/assets/ic-data-table-CKUNTxHw.js +0 -277
  95. package/node_modules/@comis/web/dist/assets/ic-delivery-row-GP5Fkygs.js +0 -67
  96. package/node_modules/@comis/web/dist/assets/ic-graph-canvas-C8FuSMe1.js +0 -359
  97. package/node_modules/@comis/web/dist/assets/ic-icon-xeGTVhVG.js +0 -33
  98. package/node_modules/@comis/web/dist/assets/ic-relative-time-3FqpjeAI.js +0 -12
  99. package/node_modules/@comis/web/dist/assets/ic-tabs-B7QtM_v8.js +0 -95
  100. package/node_modules/@comis/web/dist/assets/ic-tag-CPPUnDLF.js +0 -33
  101. package/node_modules/@comis/web/dist/assets/index-CEcM1R_C.js +0 -2830
  102. package/node_modules/@comis/web/dist/assets/index-CIJFuItj.css +0 -1
  103. package/node_modules/@comis/web/dist/assets/observability-types-D7jUtSde.js +0 -1
  104. package/node_modules/@comis/web/dist/assets/pipeline-builder-DcUUIrm0.js +0 -1496
  105. package/node_modules/@comis/web/dist/assets/session-key-parser-DPORMVyU.js +0 -1
  106. package/node_modules/@comis/web/dist/assets/session-list-6ybUTxbY.js +0 -231
@@ -324,6 +324,23 @@ export function createPiExecutor(config, deps) {
324
324
  if (normalizedPrimary.normalized) {
325
325
  deps.logger.debug({ original: config.model, resolved: normalizedPrimary.modelId }, "Model ID normalized via shortcut");
326
326
  }
327
+ // Surface the silent-fallback case where pi-coding-agent picks a different
328
+ // provider than the user configured. When find() returns undefined for an
329
+ // explicit (non-default) provider/model, pi will silently shop `findInitialModel`
330
+ // and pick whatever built-in has env-var auth -- e.g., GEMINI_API_KEY → google.
331
+ // The wiring fix in setup-agents.ts should cover the YAML-provider case; this
332
+ // log catches stragglers (typos, disabled providers, missing API keys).
333
+ if (!resolvedModel
334
+ && config.provider.toLowerCase() !== "default"
335
+ && config.model.toLowerCase() !== "default") {
336
+ deps.logger.warn({
337
+ agentId,
338
+ configuredProvider: config.provider,
339
+ configuredModel: normalizedPrimary.modelId,
340
+ hint: "Provider not registered in pi ModelRegistry. Check providers.entries.<name> in config.yaml has type/baseUrl/apiKeyName set, the API key resolves via SecretManager, and the provider is enabled. Without a match, pi-coding-agent silently falls back to whatever built-in provider has env-var credentials.",
341
+ errorKind: "config",
342
+ }, "Configured provider/model not found in registry; pi-coding-agent will fall back");
343
+ }
327
344
  if (executionOverrides?.model) {
328
345
  // Model override format: "provider:modelId" (same as compactionModel pattern)
329
346
  const parts = executionOverrides.model.split(":");
@@ -135,7 +135,8 @@ export { createPiEventBridge } from "./bridge/pi-event-bridge.js";
135
135
  export type { PiEventBridgeDeps, PiEventBridgeResult } from "./bridge/pi-event-bridge.js";
136
136
  export { createAuthStorageAdapter, DEFAULT_PROVIDER_KEYS } from "./model/auth-storage-adapter.js";
137
137
  export type { AuthStorageAdapterOptions } from "./model/auth-storage-adapter.js";
138
- export { createModelRegistryAdapter, resolveInitialModel } from "./model/model-registry-adapter.js";
138
+ export { createModelRegistryAdapter, registerCustomProviders, resolveInitialModel } from "./model/model-registry-adapter.js";
139
+ export type { CustomProviderRegistration, CustomProviderLogger } from "./model/model-registry-adapter.js";
139
140
  export { sessionKeyToPath, pathToSessionKey } from "./session/session-key-mapper.js";
140
141
  export { detectBrokenFollowThrough, FOLLOW_THROUGH_PATTERNS } from "./safety/response-safety-checks.js";
141
142
  export type { FollowThroughResult } from "./safety/response-safety-checks.js";
@@ -135,7 +135,7 @@ export { createPiEventBridge } from "./bridge/pi-event-bridge.js";
135
135
  // Auth storage adapter (SecretManager to pi-coding-agent AuthStorage)
136
136
  export { createAuthStorageAdapter, DEFAULT_PROVIDER_KEYS } from "./model/auth-storage-adapter.js";
137
137
  // Model registry adapter (ModelRegistry creation + initial model resolution)
138
- export { createModelRegistryAdapter, resolveInitialModel } from "./model/model-registry-adapter.js";
138
+ export { createModelRegistryAdapter, registerCustomProviders, resolveInitialModel } from "./model/model-registry-adapter.js";
139
139
  // Session key mapper (SessionKey to/from filesystem path)
140
140
  export { sessionKeyToPath, pathToSessionKey } from "./session/session-key-mapper.js";
141
141
  // ---------------------------------------------------------------------------
@@ -10,12 +10,33 @@ import { AuthStorage } from "@mariozechner/pi-coding-agent";
10
10
  import type { SecretManager } from "@comis/core";
11
11
  /** Default provider-to-env-var mapping for known LLM providers. */
12
12
  export declare const DEFAULT_PROVIDER_KEYS: Record<string, string>;
13
+ /**
14
+ * Custom YAML provider entry projection used to populate AuthStorage with
15
+ * runtime API keys for providers declared under `providers.entries.*`.
16
+ *
17
+ * Only the fields needed for credential wiring are included -- the full
18
+ * ProviderEntry lives in @comis/core but importing it here would pull
19
+ * the entire config domain into the agent package.
20
+ */
21
+ export interface CustomProviderAuth {
22
+ /** SecretManager key name for the API key (e.g., "NVIDIA_API_KEY"). */
23
+ apiKeyName: string;
24
+ /** Whether the provider is enabled. Disabled entries are skipped. */
25
+ enabled: boolean;
26
+ }
13
27
  /** Options for creating an AuthStorage adapter. */
14
28
  export interface AuthStorageAdapterOptions {
15
29
  /** SecretManager to read API keys from. */
16
30
  secretManager: SecretManager;
17
31
  /** Additional provider-to-env-var mappings beyond the defaults. */
18
32
  additionalProviderKeys?: Record<string, string>;
33
+ /**
34
+ * Custom YAML provider entries (`providers.entries.*`). Each entry's
35
+ * `apiKeyName` is resolved through `secretManager` and registered as a
36
+ * runtime override on the returned AuthStorage. Disabled entries and
37
+ * entries with empty `apiKeyName` are skipped silently.
38
+ */
39
+ customProviderEntries?: Record<string, CustomProviderAuth>;
19
40
  }
20
41
  /**
21
42
  * Create an AuthStorage populated with API keys from SecretManager.
@@ -24,7 +24,7 @@ export const DEFAULT_PROVIDER_KEYS = {
24
24
  * setRuntimeApiKey() for found keys. Missing keys are silently skipped.
25
25
  */
26
26
  export function createAuthStorageAdapter(options) {
27
- const { secretManager, additionalProviderKeys } = options;
27
+ const { secretManager, additionalProviderKeys, customProviderEntries } = options;
28
28
  const storage = AuthStorage.fromStorage(new InMemoryAuthStorageBackend());
29
29
  const allProviderKeys = { ...DEFAULT_PROVIDER_KEYS, ...additionalProviderKeys };
30
30
  for (const [provider, envKey] of Object.entries(allProviderKeys)) {
@@ -33,5 +33,19 @@ export function createAuthStorageAdapter(options) {
33
33
  storage.setRuntimeApiKey(provider, apiKey);
34
34
  }
35
35
  }
36
+ // Custom YAML providers (providers.entries.*). Runtime overrides take
37
+ // priority over auth.json and env-var fallback in pi-coding-agent, so
38
+ // YAML config wins over any stray env keys (e.g., GEMINI_API_KEY) that
39
+ // might otherwise satisfy hasAuth() for an unrelated built-in provider.
40
+ if (customProviderEntries) {
41
+ for (const [providerName, entry] of Object.entries(customProviderEntries)) {
42
+ if (!entry.enabled || !entry.apiKeyName)
43
+ continue;
44
+ const apiKey = secretManager.get(entry.apiKeyName);
45
+ if (apiKey) {
46
+ storage.setRuntimeApiKey(providerName, apiKey);
47
+ }
48
+ }
49
+ }
36
50
  return storage;
37
51
  }
@@ -12,6 +12,7 @@ import { ModelRegistry } from "@mariozechner/pi-coding-agent";
12
12
  import type { AuthStorage } from "@mariozechner/pi-coding-agent";
13
13
  import type { Api, Model } from "@mariozechner/pi-ai";
14
14
  import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
15
+ import type { SecretManager } from "@comis/core";
15
16
  import type { ModelAllowlist } from "./model-allowlist.js";
16
17
  /** Result of initial model resolution. */
17
18
  export interface InitialModelResult {
@@ -30,6 +31,51 @@ export interface InitialModelResult {
30
31
  * have API keys configured in AuthStorage.
31
32
  */
32
33
  export declare function createModelRegistryAdapter(authStorage: AuthStorage): ModelRegistry;
34
+ /** Subset of `ProviderEntry` (from `@comis/core`) we read for pi registration. */
35
+ export interface CustomProviderRegistration {
36
+ type: string;
37
+ baseUrl: string;
38
+ apiKeyName: string;
39
+ enabled: boolean;
40
+ headers: Record<string, string>;
41
+ models: ReadonlyArray<{
42
+ id: string;
43
+ name?: string;
44
+ contextWindow?: number;
45
+ maxTokens?: number;
46
+ reasoning?: boolean;
47
+ input?: ReadonlyArray<"text" | "image">;
48
+ cost?: {
49
+ input?: number;
50
+ output?: number;
51
+ cacheRead?: number;
52
+ cacheWrite?: number;
53
+ };
54
+ }>;
55
+ }
56
+ /** Logger surface accepted by `registerCustomProviders`. Subset of Pino. */
57
+ export interface CustomProviderLogger {
58
+ warn(obj: Record<string, unknown>, msg: string): void;
59
+ debug(obj: Record<string, unknown>, msg: string): void;
60
+ }
61
+ /**
62
+ * Register YAML `providers.entries.*` with pi-coding-agent's ModelRegistry.
63
+ *
64
+ * Without this, custom OpenAI-compatible providers (NVIDIA NIM, Together,
65
+ * ollama, etc.) are not findable via `registry.find(provider, modelId)`,
66
+ * which causes pi's `findInitialModel` to silently fall back to whatever
67
+ * built-in provider has env-var auth (e.g., GEMINI_API_KEY → google).
68
+ *
69
+ * Per-entry behavior:
70
+ * - Skipped if `enabled === false`.
71
+ * - Skipped if no models declared and no `baseUrl` override.
72
+ * - On `registerProvider` error (missing baseUrl, missing apiKey, etc.),
73
+ * a WARN is logged and the loop continues -- one bad entry must not
74
+ * prevent the daemon from starting.
75
+ *
76
+ * @returns Number of entries successfully registered.
77
+ */
78
+ export declare function registerCustomProviders(registry: ModelRegistry, entries: Record<string, CustomProviderRegistration>, secretManager: SecretManager, logger: CustomProviderLogger): number;
33
79
  /**
34
80
  * Resolve the initial model for an agent session.
35
81
  *
@@ -20,6 +20,114 @@ import { ModelRegistry } from "@mariozechner/pi-coding-agent";
20
20
  export function createModelRegistryAdapter(authStorage) {
21
21
  return ModelRegistry.inMemory(authStorage);
22
22
  }
23
+ /**
24
+ * YAML provider type → pi-ai API identifier. Mirrors the
25
+ * `OPENAI_COMPATIBLE_TYPES` set in `model-scanner.ts`. Unknown types
26
+ * default to `openai-completions` so arbitrary OpenAI-compatible
27
+ * proxies (NVIDIA NIM, Together, ollama, lm-studio, etc.) work without
28
+ * code changes.
29
+ */
30
+ const PROVIDER_TYPE_TO_API = {
31
+ openai: "openai-completions",
32
+ groq: "openai-completions",
33
+ mistral: "openai-completions",
34
+ together: "openai-completions",
35
+ deepseek: "openai-completions",
36
+ cerebras: "openai-completions",
37
+ xai: "openai-completions",
38
+ openrouter: "openai-completions",
39
+ anthropic: "anthropic-messages",
40
+ google: "google-generative-ai",
41
+ };
42
+ /**
43
+ * Register YAML `providers.entries.*` with pi-coding-agent's ModelRegistry.
44
+ *
45
+ * Without this, custom OpenAI-compatible providers (NVIDIA NIM, Together,
46
+ * ollama, etc.) are not findable via `registry.find(provider, modelId)`,
47
+ * which causes pi's `findInitialModel` to silently fall back to whatever
48
+ * built-in provider has env-var auth (e.g., GEMINI_API_KEY → google).
49
+ *
50
+ * Per-entry behavior:
51
+ * - Skipped if `enabled === false`.
52
+ * - Skipped if no models declared and no `baseUrl` override.
53
+ * - On `registerProvider` error (missing baseUrl, missing apiKey, etc.),
54
+ * a WARN is logged and the loop continues -- one bad entry must not
55
+ * prevent the daemon from starting.
56
+ *
57
+ * @returns Number of entries successfully registered.
58
+ */
59
+ export function registerCustomProviders(registry, entries, secretManager, logger) {
60
+ let registered = 0;
61
+ for (const [providerName, entry] of Object.entries(entries)) {
62
+ if (!entry.enabled) {
63
+ logger.debug({ providerName }, "Custom provider skipped (disabled)");
64
+ continue;
65
+ }
66
+ const hasModels = entry.models.length > 0;
67
+ const hasBaseUrlOverride = !!entry.baseUrl;
68
+ if (!hasModels && !hasBaseUrlOverride) {
69
+ logger.debug({ providerName }, "Custom provider skipped (no models and no baseUrl override)");
70
+ continue;
71
+ }
72
+ const apiKey = entry.apiKeyName ? secretManager.get(entry.apiKeyName) : undefined;
73
+ if (hasModels && !apiKey) {
74
+ logger.warn({
75
+ providerName,
76
+ apiKeyName: entry.apiKeyName,
77
+ hint: "Set the named secret in ~/.comis/.env or remove the provider entry from config.yaml",
78
+ errorKind: "config",
79
+ }, "Custom provider has models but no API key -- skipping registration");
80
+ continue;
81
+ }
82
+ const api = PROVIDER_TYPE_TO_API[entry.type] ?? "openai-completions";
83
+ const headersResolved = Object.keys(entry.headers).length > 0 ? entry.headers : undefined;
84
+ try {
85
+ registry.registerProvider(providerName, {
86
+ api,
87
+ baseUrl: entry.baseUrl || undefined,
88
+ apiKey,
89
+ headers: headersResolved,
90
+ // pi's ProviderModelConfig requires concrete values for name/cost/
91
+ // contextWindow/maxTokens. Comis's UserModelSchema lets users omit
92
+ // these (defaults to optional/undefined), so we fill in zeros and
93
+ // a generous default context window. Cost is informational only
94
+ // and our CostTracker uses pi-ai's own catalog where it can.
95
+ models: hasModels
96
+ ? entry.models.map((m) => ({
97
+ id: m.id,
98
+ name: m.name ?? m.id,
99
+ contextWindow: m.contextWindow ?? 128_000,
100
+ maxTokens: m.maxTokens ?? 4_096,
101
+ reasoning: m.reasoning ?? false,
102
+ input: m.input ? [...m.input] : ["text"],
103
+ cost: {
104
+ input: m.cost?.input ?? 0,
105
+ output: m.cost?.output ?? 0,
106
+ cacheRead: m.cost?.cacheRead ?? 0,
107
+ cacheWrite: m.cost?.cacheWrite ?? 0,
108
+ },
109
+ }))
110
+ : undefined,
111
+ });
112
+ registered += 1;
113
+ logger.debug({
114
+ providerName,
115
+ api,
116
+ baseUrl: entry.baseUrl,
117
+ modelCount: entry.models.length,
118
+ }, "Custom provider registered with pi ModelRegistry");
119
+ }
120
+ catch (error) {
121
+ logger.warn({
122
+ providerName,
123
+ err: error instanceof Error ? error.message : String(error),
124
+ hint: "Check providers.entries config: baseUrl required when defining models; apiKey required unless oauth configured",
125
+ errorKind: "config",
126
+ }, "Custom provider registration failed");
127
+ }
128
+ }
129
+ return registered;
130
+ }
23
131
  /**
24
132
  * Resolve the initial model for an agent session.
25
133
  *
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/agent",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "AI agent executor, budget control, and session management for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/channels",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Chat platform adapters — Discord, Telegram, Slack, WhatsApp, Signal, iMessage, IRC, LINE",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/cli",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Command-line interface for the Comis AI agent platform",
@@ -2,6 +2,7 @@ import { ok, err } from "@comis/shared";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { loadLayered } from "./config/layered.js";
5
+ import { buildGatewayEnvLayer } from "./config/env-layer.js";
5
6
  import { TypedEventBus } from "./event-bus/index.js";
6
7
  import { createSecretManager, safePath } from "./security/index.js";
7
8
  import { createPluginRegistry } from "./hooks/plugin-registry.js";
@@ -44,12 +45,16 @@ export function bootstrap(options) {
44
45
  // Wrap getSecret to record every name referenced by the config — the set
45
46
  // becomes container.platformSecretNames and is used by the exec tool to
46
47
  // refuse secretRefs access to platform-managed credentials.
48
+ // envLayer projects operational env vars (COMIS_GATEWAY_HOST/PORT) into
49
+ // the config layer stack at lower priority than YAML files, so explicit
50
+ // user config wins over env — see config/env-layer.ts.
47
51
  const referencedNames = new Set();
48
52
  const configResult = loadLayered(options.configPaths, {
49
53
  getSecret: (key) => {
50
54
  referencedNames.add(key);
51
55
  return secretManager.get(key);
52
56
  },
57
+ envLayer: buildGatewayEnvLayer(env),
53
58
  });
54
59
  if (!configResult.ok) {
55
60
  return err(configResult.error);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Operational env-var → config-layer projection.
3
+ *
4
+ * Bridges container/systemd/pm2 deployments to layered config without a
5
+ * config.yaml. Currently supported:
6
+ *
7
+ * COMIS_GATEWAY_HOST → gateway.host (e.g. "0.0.0.0" inside the Docker image)
8
+ * COMIS_GATEWAY_PORT → gateway.port
9
+ *
10
+ * The returned object is a partial config layer fed to mergeLayered() at
11
+ * lower priority than YAML files: schema defaults < env layer < config.yaml.
12
+ * This keeps explicit user config authoritative — a `gateway.host: 127.0.0.1`
13
+ * in config.yaml is never silently broadened to 0.0.0.0 by an inherited env
14
+ * var, preserving the secure-by-default contract on `gateway.host`.
15
+ *
16
+ * Empty-string host and non-numeric / out-of-range ports are dropped so a
17
+ * typo never silently relocates the daemon.
18
+ *
19
+ * @module
20
+ */
21
+ /** Subset of env vars consumed by this projection. */
22
+ export interface GatewayEnvSource {
23
+ COMIS_GATEWAY_HOST?: string | undefined;
24
+ COMIS_GATEWAY_PORT?: string | undefined;
25
+ }
26
+ /**
27
+ * Build a partial config layer from env vars. Returns an empty object when
28
+ * no relevant env vars are set (callers can pass through to mergeLayered
29
+ * unconditionally without affecting precedence).
30
+ */
31
+ export declare function buildGatewayEnvLayer(env: GatewayEnvSource): Record<string, unknown>;
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Operational env-var → config-layer projection.
4
+ *
5
+ * Bridges container/systemd/pm2 deployments to layered config without a
6
+ * config.yaml. Currently supported:
7
+ *
8
+ * COMIS_GATEWAY_HOST → gateway.host (e.g. "0.0.0.0" inside the Docker image)
9
+ * COMIS_GATEWAY_PORT → gateway.port
10
+ *
11
+ * The returned object is a partial config layer fed to mergeLayered() at
12
+ * lower priority than YAML files: schema defaults < env layer < config.yaml.
13
+ * This keeps explicit user config authoritative — a `gateway.host: 127.0.0.1`
14
+ * in config.yaml is never silently broadened to 0.0.0.0 by an inherited env
15
+ * var, preserving the secure-by-default contract on `gateway.host`.
16
+ *
17
+ * Empty-string host and non-numeric / out-of-range ports are dropped so a
18
+ * typo never silently relocates the daemon.
19
+ *
20
+ * @module
21
+ */
22
+ /**
23
+ * Build a partial config layer from env vars. Returns an empty object when
24
+ * no relevant env vars are set (callers can pass through to mergeLayered
25
+ * unconditionally without affecting precedence).
26
+ */
27
+ export function buildGatewayEnvLayer(env) {
28
+ const gateway = {};
29
+ const rawHost = env.COMIS_GATEWAY_HOST;
30
+ if (typeof rawHost === "string" && rawHost.length > 0) {
31
+ gateway["host"] = rawHost;
32
+ }
33
+ const rawPort = env.COMIS_GATEWAY_PORT;
34
+ if (typeof rawPort === "string" && rawPort.length > 0) {
35
+ const parsed = Number.parseInt(rawPort, 10);
36
+ if (Number.isFinite(parsed) && parsed >= 1 && parsed <= 65_535) {
37
+ gateway["port"] = parsed;
38
+ }
39
+ }
40
+ return Object.keys(gateway).length > 0 ? { gateway } : {};
41
+ }
@@ -24,7 +24,16 @@ export declare function mergeLayered(layers: Record<string, unknown>[]): Result<
24
24
  * This supports the common pattern: defaults.yaml < config.yaml < config.local.yaml
25
25
  *
26
26
  * If any file fails to load, returns the error immediately.
27
+ *
28
+ * `envLayer` (when provided) is prepended to the layer sequence so it sits
29
+ * between Zod schema defaults and YAML files in precedence order:
30
+ * defaults < envLayer < config files
31
+ * Explicit user config in YAML always wins over env-derived values, which
32
+ * preserves secure-by-default semantics for security-sensitive fields like
33
+ * `gateway.host` — an inherited env var can never silently broaden a bind
34
+ * the operator pinned in config.yaml.
27
35
  */
28
36
  export declare function loadLayered(configPaths: string[], options?: {
29
37
  getSecret?: (key: string) => string | undefined;
38
+ envLayer?: Record<string, unknown>;
30
39
  }): Result<AppConfig, ConfigError>;
@@ -73,9 +73,20 @@ export function mergeLayered(layers) {
73
73
  * This supports the common pattern: defaults.yaml < config.yaml < config.local.yaml
74
74
  *
75
75
  * If any file fails to load, returns the error immediately.
76
+ *
77
+ * `envLayer` (when provided) is prepended to the layer sequence so it sits
78
+ * between Zod schema defaults and YAML files in precedence order:
79
+ * defaults < envLayer < config files
80
+ * Explicit user config in YAML always wins over env-derived values, which
81
+ * preserves secure-by-default semantics for security-sensitive fields like
82
+ * `gateway.host` — an inherited env var can never silently broaden a bind
83
+ * the operator pinned in config.yaml.
76
84
  */
77
85
  export function loadLayered(configPaths, options) {
78
86
  const layers = [];
87
+ if (options?.envLayer && Object.keys(options.envLayer).length > 0) {
88
+ layers.push(options.envLayer);
89
+ }
79
90
  for (const configPath of configPaths) {
80
91
  const result = loadConfigFile(configPath, options?.getSecret ? { getSecret: options.getSecret } : undefined);
81
92
  if (!result.ok) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/core",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Core domain types, ports, event bus, security, and config for Comis",
@@ -1044,6 +1044,9 @@ export async function main(overrides = {}) {
1044
1044
  };
1045
1045
  wireDispatch(rpcDispatchDeps);
1046
1046
  // 7. Gateway
1047
+ // gateway.host / .port are resolved through the layered config in bootstrap:
1048
+ // schema defaults < env layer (COMIS_GATEWAY_HOST/PORT) < config.yaml.
1049
+ // See packages/core/src/config/env-layer.ts.
1047
1050
  const gwConfig = container.config.gateway;
1048
1051
  const { gatewayHandle, activeExecutions, getActiveConnectionCount, wsConnections } = await setupGateway({
1049
1052
  container, gwConfig, webhooksConfig: container.config.webhooks, agents, defaultAgentId,
@@ -12,7 +12,7 @@ import { createHmac } from "node:crypto";
12
12
  import { homedir } from "node:os";
13
13
  import { existsSync, mkdirSync } from "node:fs";
14
14
  import { isAbsolute, resolve } from "node:path";
15
- import { createCircuitBreaker, createBudgetGuard, createCostTracker, createStepCounter, createSessionLifecycle, ensureWorkspace, resolveWorkspaceDir, createPiExecutor, createComisSessionManager, cleanupStaleLocks, createAuthStorageAdapter, createModelRegistryAdapter, createProviderHealthMonitor, setSanitizeLogger, setToolNormalizationLogger, LEAN_TOOL_DESCRIPTIONS, resolveDescription, } from "@comis/agent";
15
+ import { createCircuitBreaker, createBudgetGuard, createCostTracker, createStepCounter, createSessionLifecycle, ensureWorkspace, resolveWorkspaceDir, createPiExecutor, createComisSessionManager, cleanupStaleLocks, createAuthStorageAdapter, createModelRegistryAdapter, registerCustomProviders, createProviderHealthMonitor, setSanitizeLogger, setToolNormalizationLogger, LEAN_TOOL_DESCRIPTIONS, resolveDescription, } from "@comis/agent";
16
16
  import { agentToolsToToolDefinitions, createSkillRegistry, createRuntimeEligibilityContext, TOOL_PROFILES, } from "@comis/skills";
17
17
  // ---------------------------------------------------------------------------
18
18
  // Single-agent setup (extracted for hot-add reuse)
@@ -60,9 +60,21 @@ export async function setupSingleAgent(agentId, rawAgentConfig, deps) {
60
60
  eventBus: container.eventBus,
61
61
  });
62
62
  agentLogger.debug({ agentId, allowPatterns: agentSecrets.allow }, "Per-agent ScopedSecretManager created");
63
- // Per-agent auth + model registry (moved from shared to per-agent for credential isolation)
64
- const piAuthStorage = createAuthStorageAdapter({ secretManager: scopedManager });
63
+ // Per-agent auth + model registry (moved from shared to per-agent for credential isolation).
64
+ // Custom YAML providers under `providers.entries.*` are wired into both auth (runtime API
65
+ // key overrides) and the registry (so `find(provider, modelId)` succeeds) -- without this,
66
+ // pi-coding-agent silently falls back to whatever built-in provider has env-var auth (e.g.,
67
+ // GEMINI_API_KEY → google), bypassing the configured provider entirely.
68
+ const customProviderEntries = container.config.providers?.entries ?? {};
69
+ const piAuthStorage = createAuthStorageAdapter({
70
+ secretManager: scopedManager,
71
+ customProviderEntries,
72
+ });
65
73
  const piModelRegistry = createModelRegistryAdapter(piAuthStorage);
74
+ const customProviderCount = registerCustomProviders(piModelRegistry, customProviderEntries, scopedManager, agentLogger);
75
+ if (customProviderCount > 0) {
76
+ agentLogger.debug({ agentId, customProviderCount }, "Custom YAML providers registered with pi ModelRegistry");
77
+ }
66
78
  // Create JSONL session adapter for this agent
67
79
  const lockDir = safePath(dir, ".locks");
68
80
  const sessionAdapter = createComisSessionManager({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/daemon",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Background daemon and orchestrator for the Comis platform",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/gateway",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "HTTP, JSON-RPC, and WebSocket gateway for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/infra",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Structured logging infrastructure for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/memory",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "SQLite memory, embeddings, and RAG storage for Comis agents",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/scheduler",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Task scheduling and cron management for Comis",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/shared",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Shared types and utilities for the Comis platform",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@comis/skills",
3
3
  "private": true,
4
- "version": "1.0.23",
4
+ "version": "1.0.25",
5
5
  "author": "Moshe Anconina",
6
6
  "license": "Apache-2.0",
7
7
  "description": "Skill system, MCP integration, and tool sandbox for Comis agents",