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.
- package/node_modules/@comis/agent/dist/executor/pi-executor.js +17 -0
- package/node_modules/@comis/agent/dist/index.d.ts +2 -1
- package/node_modules/@comis/agent/dist/index.js +1 -1
- package/node_modules/@comis/agent/dist/model/auth-storage-adapter.d.ts +21 -0
- package/node_modules/@comis/agent/dist/model/auth-storage-adapter.js +15 -1
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.d.ts +46 -0
- package/node_modules/@comis/agent/dist/model/model-registry-adapter.js +108 -0
- package/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/bootstrap.js +5 -0
- package/node_modules/@comis/core/dist/config/env-layer.d.ts +31 -0
- package/node_modules/@comis/core/dist/config/env-layer.js +41 -0
- package/node_modules/@comis/core/dist/config/layered.d.ts +9 -0
- package/node_modules/@comis/core/dist/config/layered.js +11 -0
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/dist/daemon.js +3 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-agents.js +15 -3
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/package.json +1 -1
- package/node_modules/@comis/memory/package.json +1 -1
- package/node_modules/@comis/scheduler/package.json +1 -1
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/package.json +1 -1
- package/node_modules/@comis/web/dist/assets/{agent-detail-BG9MGWWj.js → agent-detail-ru-AhppM.js} +270 -270
- package/node_modules/@comis/web/dist/assets/agent-editor-hjwRuFVp.js +2173 -0
- package/node_modules/@comis/web/dist/assets/{agent-list-LHCJ4rw2.js → agent-list-6Uotjatr.js} +170 -170
- package/node_modules/@comis/web/dist/assets/{approvals-q9VH_IKr.js → approvals-C-K6hN2U.js} +13 -13
- package/node_modules/@comis/web/dist/assets/billing-view-CxysXH0p.js +375 -0
- package/node_modules/@comis/web/dist/assets/{channel-detail-CaInesJM.js → channel-detail-BBCKtmne.js} +265 -265
- package/node_modules/@comis/web/dist/assets/channel-list-FkfeOLBQ.js +323 -0
- package/node_modules/@comis/web/dist/assets/{chat-console-CNmzl0JW.js → chat-console-BumBaIgO.js} +243 -246
- package/node_modules/@comis/web/dist/assets/{config-editor-DX4ITw6y.js → config-editor-C9BSwHGy.js} +477 -477
- package/node_modules/@comis/web/dist/assets/{context-dag-browser-BwiaF5tf.js → context-dag-browser-BHm00mJD.js} +105 -105
- package/node_modules/@comis/web/dist/assets/{context-engine-BZ5Am6hA.js → context-engine-BENY3pWE.js} +136 -136
- package/node_modules/@comis/web/dist/assets/decorate-BvWYovGE.js +38 -0
- package/node_modules/@comis/web/dist/assets/{delivery-view-OfBZof-m.js → delivery-view-BCnkPsAp.js} +134 -134
- package/node_modules/@comis/web/dist/assets/{diagnostics-view-YFwCxgr2.js → diagnostics-view-C_jQFG2H.js} +82 -82
- package/node_modules/@comis/web/dist/assets/directive-BOYXJ-K-.js +1 -0
- package/node_modules/@comis/web/dist/assets/{extract-variables-BM5qyK-s.js → extract-variables-B7-Doo7l.js} +39 -39
- package/node_modules/@comis/web/dist/assets/{ic-array-editor-B7m6x7-S.js → ic-array-editor-BLoEyeLS.js} +29 -29
- package/node_modules/@comis/web/dist/assets/{ic-breadcrumb-CUMpp3BL.js → ic-breadcrumb-DqN6G3gc.js} +16 -16
- package/node_modules/@comis/web/dist/assets/{ic-budget-segment-bar-BtJ6x5mN.js → ic-budget-segment-bar-zLsMzjDO.js} +20 -20
- package/node_modules/@comis/web/dist/assets/ic-chat-message-FdQcZsSQ.js +352 -0
- package/node_modules/@comis/web/dist/assets/{ic-confirm-dialog-CCDbB04e.js → ic-confirm-dialog-DGlPbV1T.js} +26 -26
- package/node_modules/@comis/web/dist/assets/{ic-connection-dot-CnT1b8xr.js → ic-connection-dot-BgYiK2N4.js} +13 -13
- package/node_modules/@comis/web/dist/assets/ic-data-table-CKIvr-ag.js +277 -0
- package/node_modules/@comis/web/dist/assets/ic-delivery-row-B3YwjjuM.js +67 -0
- package/node_modules/@comis/web/dist/assets/{ic-detail-panel-BF83r-if.js → ic-detail-panel-DiCe4hLr.js} +27 -27
- package/node_modules/@comis/web/dist/assets/{ic-empty-state-60l2ePhd.js → ic-empty-state-CM3Wbj2f.js} +19 -19
- package/node_modules/@comis/web/dist/assets/ic-graph-canvas-ByRjij68.js +359 -0
- package/node_modules/@comis/web/dist/assets/ic-icon-BGNCCPpZ.js +33 -0
- package/node_modules/@comis/web/dist/assets/{ic-layer-waterfall-COvEYMg5.js → ic-layer-waterfall-WkaFyu-l.js} +18 -18
- package/node_modules/@comis/web/dist/assets/ic-relative-time-B3UAnTqg.js +12 -0
- package/node_modules/@comis/web/dist/assets/{ic-search-input-CSOxY9g7.js → ic-search-input-B02AGw1i.js} +22 -22
- package/node_modules/@comis/web/dist/assets/{ic-select-Ce-Raudx.js → ic-select-BqfZISjw.js} +29 -29
- package/node_modules/@comis/web/dist/assets/ic-tabs-yBjkWKJH.js +95 -0
- package/node_modules/@comis/web/dist/assets/ic-tag-CvMVQFRR.js +33 -0
- package/node_modules/@comis/web/dist/assets/{ic-time-range-picker-CypCT68y.js → ic-time-range-picker-DXbYeBmY.js} +31 -31
- package/node_modules/@comis/web/dist/assets/{ic-tool-call-7MaXSsCW.js → ic-tool-call-DMPHsLyx.js} +51 -51
- package/node_modules/@comis/web/dist/assets/index-CVEaS9aY.css +2 -0
- package/node_modules/@comis/web/dist/assets/index-FLPhHz8p.js +2792 -0
- package/node_modules/@comis/web/dist/assets/{mcp-management-BNZPnpDn.js → mcp-management-5jyScQis.js} +209 -209
- package/node_modules/@comis/web/dist/assets/{media-config-BBvTYxOX.js → media-config-J9oT9PPs.js} +154 -154
- package/node_modules/@comis/web/dist/assets/{media-test-BkK3RCRK.js → media-test-DGTCtM8-.js} +259 -259
- package/node_modules/@comis/web/dist/assets/{memory-inspector-1hDGCGat.js → memory-inspector-D5Re9ptG.js} +450 -450
- package/node_modules/@comis/web/dist/assets/{message-center-CXefwsUu.js → message-center-cRLK6ZmG.js} +290 -290
- package/node_modules/@comis/web/dist/assets/{models-C1qcU_j3.js → models-D5vu07MR.js} +371 -371
- package/node_modules/@comis/web/dist/assets/observability-types-D0tkwElU.js +1 -0
- package/node_modules/@comis/web/dist/assets/{observe-view-C0VBhX4C.js → observe-view-CalNNEmd.js} +399 -399
- package/node_modules/@comis/web/dist/assets/pipeline-builder-DUYDGwZf.js +1495 -0
- package/node_modules/@comis/web/dist/assets/{pipeline-history-DkfOQ6SW.js → pipeline-history-BAO8brOe.js} +124 -124
- package/node_modules/@comis/web/dist/assets/{pipeline-history-detail-hyHgD0ai.js → pipeline-history-detail-DectIoQt.js} +65 -65
- package/node_modules/@comis/web/dist/assets/{pipeline-list-BPW8hV-q.js → pipeline-list-BHlaBKww.js} +227 -227
- package/node_modules/@comis/web/dist/assets/{pipeline-monitor-Bip16T7e.js → pipeline-monitor-BhtpNEHf.js} +298 -298
- package/node_modules/@comis/web/dist/assets/{scheduler-BGgwKd06.js → scheduler-VafN_8xi.js} +486 -486
- package/node_modules/@comis/web/dist/assets/{security-D15st4xx.js → security-QQXMRTlo.js} +389 -389
- package/node_modules/@comis/web/dist/assets/{session-detail-SGEYNJ0M.js → session-detail-BpZ_8Yih.js} +294 -294
- package/node_modules/@comis/web/dist/assets/session-key-parser-Dkqcj2Ss.js +1 -0
- package/node_modules/@comis/web/dist/assets/session-list-DfCm8Cec.js +231 -0
- package/node_modules/@comis/web/dist/assets/{setup-wizard-nT0tz9QP.js → setup-wizard-C-z477CG.js} +486 -494
- package/node_modules/@comis/web/dist/assets/{skills-D8yVfSUy.js → skills-BCOGPf6s.js} +329 -329
- package/node_modules/@comis/web/dist/assets/{subagents-HHXMeHYo.js → subagents-l-auUraL.js} +74 -74
- package/node_modules/@comis/web/dist/assets/{workspace-manager-BQlr10iH.js → workspace-manager-DlvBixiq.js} +236 -236
- package/node_modules/@comis/web/dist/index.html +3 -2
- package/node_modules/@comis/web/package.json +1 -1
- package/package.json +15 -15
- package/node_modules/@comis/web/dist/assets/agent-editor-C26Q_xCs.js +0 -2173
- package/node_modules/@comis/web/dist/assets/billing-view-CtYvBqTE.js +0 -375
- package/node_modules/@comis/web/dist/assets/channel-list-B8dj3O9a.js +0 -323
- package/node_modules/@comis/web/dist/assets/directive-DoeGSK_T.js +0 -1
- package/node_modules/@comis/web/dist/assets/ic-chat-message-CFyDJd0z.js +0 -352
- package/node_modules/@comis/web/dist/assets/ic-data-table-CKUNTxHw.js +0 -277
- package/node_modules/@comis/web/dist/assets/ic-delivery-row-GP5Fkygs.js +0 -67
- package/node_modules/@comis/web/dist/assets/ic-graph-canvas-C8FuSMe1.js +0 -359
- package/node_modules/@comis/web/dist/assets/ic-icon-xeGTVhVG.js +0 -33
- package/node_modules/@comis/web/dist/assets/ic-relative-time-3FqpjeAI.js +0 -12
- package/node_modules/@comis/web/dist/assets/ic-tabs-B7QtM_v8.js +0 -95
- package/node_modules/@comis/web/dist/assets/ic-tag-CPPUnDLF.js +0 -33
- package/node_modules/@comis/web/dist/assets/index-CEcM1R_C.js +0 -2830
- package/node_modules/@comis/web/dist/assets/index-CIJFuItj.css +0 -1
- package/node_modules/@comis/web/dist/assets/observability-types-D7jUtSde.js +0 -1
- package/node_modules/@comis/web/dist/assets/pipeline-builder-DcUUIrm0.js +0 -1496
- package/node_modules/@comis/web/dist/assets/session-key-parser-DPORMVyU.js +0 -1
- 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
|
*
|
|
@@ -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) {
|
|
@@ -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
|
-
|
|
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({
|