evolcore 0.0.35 → 0.0.36

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 (224) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +134 -48
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +109 -34
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -0,0 +1,382 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { isIP } from 'node:net';
3
+ const PROHIBITED_PATH_SEGMENTS = new Set(['__proto__', 'prototype', 'constructor']);
4
+ const MULTIPART_DOMAIN_SUFFIXES = new Set(['com.cn', 'net.cn', 'org.cn', 'gov.cn', 'co.uk', 'org.uk', 'ac.uk', 'com.au', 'net.au', 'co.jp', 'co.nz']);
5
+ const PROVIDER_ENV_REFERENCE = /^\$(?:([A-Za-z_][A-Za-z0-9_]*)|\{([A-Za-z_][A-Za-z0-9_]*)\})$/u;
6
+ const PROVIDER_ENV_SYNTAX = /\$\{|\$ENV:|\$[A-Za-z_][A-Za-z0-9_]*/u;
7
+ export function providerEnvironmentVariable(value) {
8
+ const match = PROVIDER_ENV_REFERENCE.exec(value);
9
+ return match?.[1] || match?.[2];
10
+ }
11
+ function assertProviderEnvironmentSyntax(value, field) {
12
+ if (PROVIDER_ENV_SYNTAX.test(value) && !providerEnvironmentVariable(value)) {
13
+ throw new Error(`${field} must use a whole-field environment reference ($VAR or \${VAR})`);
14
+ }
15
+ }
16
+ function resolveProviderEnvironmentValue(providerId, field, value) {
17
+ const variable = providerEnvironmentVariable(value);
18
+ if (!variable)
19
+ return value;
20
+ const resolved = process.env[variable];
21
+ if (resolved === undefined || !resolved.trim()) {
22
+ throw new Error(`Provider ${providerId} common.${field} references unset or empty environment variable ${variable}`);
23
+ }
24
+ return field === 'baseUrl' ? resolved.trim() : resolved;
25
+ }
26
+ function validProviderBaseUrl(value) {
27
+ try {
28
+ const parsed = new URL(value);
29
+ return ['http:', 'https:'].includes(parsed.protocol) && !parsed.username && !parsed.password && !parsed.hash && !parsed.search;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ export function providerNameFromBaseUrl(baseUrl) {
36
+ const hostname = new URL(baseUrl).hostname.replace(/\.$/u, '');
37
+ if (isIP(hostname.replace(/^\[|\]$/gu, '')))
38
+ return hostname;
39
+ const parts = hostname.split('.');
40
+ const suffixSize = MULTIPART_DOMAIN_SUFFIXES.has(parts.slice(-2).join('.')) ? 2 : 1;
41
+ return parts.length > suffixSize ? parts[parts.length - suffixSize - 1] : hostname;
42
+ }
43
+ export function providerNameFromBaseUrlIfStatic(baseUrl) {
44
+ return providerEnvironmentVariable(baseUrl) ? undefined : providerNameFromBaseUrl(baseUrl);
45
+ }
46
+ export function providerServiceTypeForPurpose(purpose) {
47
+ return purpose === 'auxiliary' ? 'text' : purpose;
48
+ }
49
+ export function providerServiceTypesForBaseagent(baseagent) {
50
+ return ['claude', 'codex', 'gemini'].includes(baseagent)
51
+ ? [baseagent]
52
+ : [...new Set(['text', baseagent])];
53
+ }
54
+ export function providerServiceTypeForBaseagent(baseagent) {
55
+ return ['ecagent', 'openclaw', 'hermes'].includes(baseagent) ? 'text' : baseagent;
56
+ }
57
+ function resolveProviderServiceTypeForBaseagent(baseagent, selectedServiceType) {
58
+ assertSafeRegistrySegment(baseagent, 'baseagent');
59
+ if (selectedServiceType !== undefined) {
60
+ assertSafeRegistrySegment(selectedServiceType, 'provider service type');
61
+ if (!providerServiceTypesForBaseagent(baseagent).includes(selectedServiceType)) {
62
+ throw new Error(`Provider service ${selectedServiceType} is incompatible with baseagent ${baseagent}`);
63
+ }
64
+ return selectedServiceType;
65
+ }
66
+ return providerServiceTypeForBaseagent(baseagent);
67
+ }
68
+ const FIXED_STYLES = {
69
+ claude: 'claude',
70
+ codex: 'codex',
71
+ gemini: 'gemini',
72
+ };
73
+ function isRecord(value) {
74
+ return !!value && typeof value === 'object' && !Array.isArray(value);
75
+ }
76
+ function nonEmpty(value, field) {
77
+ if (typeof value !== 'string' || !value.trim())
78
+ throw new Error(`${field} must be a non-empty string`);
79
+ return value.trim();
80
+ }
81
+ function assertSafeRegistrySegment(value, field) {
82
+ if (!/^[A-Za-z0-9_-]+$/u.test(value) || PROHIBITED_PATH_SEGMENTS.has(value)) {
83
+ throw new Error(`${field} must be a safe identifier`);
84
+ }
85
+ }
86
+ function assertFields(value, allowed, field) {
87
+ if (Object.keys(value).some(key => !allowed.includes(key)))
88
+ throw new Error(`${field} contains an unsupported field`);
89
+ }
90
+ function stringMap(value, field) {
91
+ if (value === undefined)
92
+ return undefined;
93
+ if (!isRecord(value))
94
+ throw new Error(`${field} must be an object`);
95
+ const result = {};
96
+ for (const [key, entry] of Object.entries(value)) {
97
+ if (!key.trim() || PROHIBITED_PATH_SEGMENTS.has(key) || typeof entry !== 'string')
98
+ throw new Error(`${field} must contain string values`);
99
+ if (field.endsWith('.headers') && (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/u.test(key) || /[\0\r\n]/u.test(entry))) {
100
+ throw new Error(`${field} contains an invalid header`);
101
+ }
102
+ result[key] = entry;
103
+ }
104
+ return result;
105
+ }
106
+ function modelOverride(value, field) {
107
+ if (!isRecord(value))
108
+ throw new Error(`${field} must be an object`);
109
+ assertFields(value, ['enabled', 'name', 'imageIn', 'manual', 'maxInputTokens'], field);
110
+ const result = {};
111
+ if (value.enabled !== undefined && value.enabled !== null && typeof value.enabled !== 'boolean') {
112
+ throw new Error(`${field}.enabled must be a boolean or null`);
113
+ }
114
+ if (value.name !== undefined && value.name !== null && typeof value.name !== 'string') {
115
+ throw new Error(`${field}.name must be a string or null`);
116
+ }
117
+ if (value.imageIn !== undefined && value.imageIn !== null && typeof value.imageIn !== 'boolean') {
118
+ throw new Error(`${field}.imageIn must be a boolean or null`);
119
+ }
120
+ if (value.manual !== undefined && typeof value.manual !== 'boolean') {
121
+ throw new Error(`${field}.manual must be a boolean`);
122
+ }
123
+ if (typeof value.enabled === 'boolean')
124
+ result.enabled = value.enabled;
125
+ if (typeof value.name === 'string')
126
+ result.name = value.name;
127
+ if (typeof value.imageIn === 'boolean')
128
+ result.imageIn = value.imageIn;
129
+ if (value.manual === true)
130
+ result.manual = true;
131
+ return result;
132
+ }
133
+ function serviceConfig(value, field, serviceType) {
134
+ if (!isRecord(value))
135
+ throw new Error(`${field} must be an object`);
136
+ assertFields(value, ['path', 'style', 'models'], field);
137
+ const result = {};
138
+ if (value.path !== undefined) {
139
+ if (typeof value.path !== 'string')
140
+ throw new Error(`${field}.path must be a string`);
141
+ if (/^[a-z][a-z\d+.-]*:/iu.test(value.path.trim()) || value.path.trim().startsWith('//') || /[\\\0\r\n?#]/u.test(value.path)) {
142
+ throw new Error(`${field}.path must be relative`);
143
+ }
144
+ result.path = value.path.trim();
145
+ }
146
+ if (value.style !== undefined)
147
+ result.style = nonEmpty(value.style, `${field}.style`);
148
+ if (value.models !== undefined) {
149
+ if (!isRecord(value.models))
150
+ throw new Error(`${field}.models must be an object`);
151
+ result.models = {};
152
+ for (const [modelId, override] of Object.entries(value.models)) {
153
+ if (!modelId.trim() || PROHIBITED_PATH_SEGMENTS.has(modelId))
154
+ throw new Error(`${field}.models contains an invalid model ID`);
155
+ const normalized = modelOverride(override, `${field}.models.${modelId}`);
156
+ const supportsImageInput = ['text', 'claude', 'codex'].includes(serviceType);
157
+ const imageMembership = !supportsImageInput && normalized.imageIn !== undefined;
158
+ if (!supportsImageInput)
159
+ delete normalized.imageIn;
160
+ if (Object.keys(normalized).length || !Object.keys(override).length || imageMembership)
161
+ result.models[modelId] = normalized;
162
+ }
163
+ if (!Object.keys(result.models).length)
164
+ delete result.models;
165
+ }
166
+ return result;
167
+ }
168
+ function providerConfig(value, field) {
169
+ if (!isRecord(value))
170
+ throw new Error(`${field} must be an object`);
171
+ assertFields(value, ['name', 'common', 'services'], field);
172
+ if (value.name !== undefined && typeof value.name !== 'string')
173
+ throw new Error(`${field}.name must be a string`);
174
+ if (!isRecord(value.common))
175
+ throw new Error(`${field}.common must be an object`);
176
+ const common = value.common;
177
+ assertFields(common, ['apiKey', 'baseUrl', 'headers', 'queryParams'], `${field}.common`);
178
+ const baseUrl = nonEmpty(common.baseUrl, `${field}.common.baseUrl`);
179
+ assertProviderEnvironmentSyntax(baseUrl, `${field}.common.baseUrl`);
180
+ if (!providerEnvironmentVariable(baseUrl) && !validProviderBaseUrl(baseUrl)) {
181
+ throw new Error(`${field}.common.baseUrl must be a valid http(s) URL`);
182
+ }
183
+ const headers = stringMap(common.headers, `${field}.common.headers`);
184
+ const queryParams = stringMap(common.queryParams, `${field}.common.queryParams`);
185
+ if (common.apiKey !== undefined && typeof common.apiKey !== 'string')
186
+ throw new Error(`${field}.common.apiKey must be a string`);
187
+ if (typeof common.apiKey === 'string')
188
+ assertProviderEnvironmentSyntax(common.apiKey, `${field}.common.apiKey`);
189
+ const result = {
190
+ ...(typeof value.name === 'string' && value.name.trim() ? { name: value.name.trim() } : {}),
191
+ common: {
192
+ baseUrl,
193
+ ...(typeof common.apiKey === 'string' ? { apiKey: common.apiKey } : {}),
194
+ ...(headers ? { headers } : {}),
195
+ ...(queryParams ? { queryParams } : {}),
196
+ },
197
+ };
198
+ if (value.services !== undefined) {
199
+ if (!isRecord(value.services))
200
+ throw new Error(`${field}.services must be an object`);
201
+ result.services = {};
202
+ for (const [sourceType, service] of Object.entries(value.services)) {
203
+ assertSafeRegistrySegment(sourceType, `${field}.services service type`);
204
+ if (sourceType === 'auxiliary')
205
+ throw new Error(`${field}.services.auxiliary is a purpose alias; configure services.text`);
206
+ // EC Agent uses the text service's image-input capability settings.
207
+ const capabilityType = sourceType === 'ecagent' ? 'text' : sourceType;
208
+ result.services[sourceType] = serviceConfig(service, `${field}.services.${sourceType}`, capabilityType);
209
+ const fixedStyle = Object.hasOwn(FIXED_STYLES, sourceType) ? FIXED_STYLES[sourceType] : undefined;
210
+ if (fixedStyle && result.services[sourceType].style && result.services[sourceType].style !== fixedStyle) {
211
+ throw new Error(`${field}.services.${sourceType} must use style ${fixedStyle}`);
212
+ }
213
+ }
214
+ }
215
+ return result;
216
+ }
217
+ export function validateProvidersFile(value) {
218
+ if (!isRecord(value))
219
+ throw new Error('providers.json must be an object');
220
+ assertFields(value, ['$schema_version', 'nextId', 'providers'], 'providers.json');
221
+ if (value.$schema_version !== undefined && (!Number.isInteger(value.$schema_version) || Number(value.$schema_version) < 1)) {
222
+ throw new Error('providers.json.$schema_version must be a positive integer');
223
+ }
224
+ if (value.nextId !== undefined && (!Number.isSafeInteger(value.nextId) || Number(value.nextId) < 1)) {
225
+ throw new Error('providers.json.nextId must be a positive safe integer');
226
+ }
227
+ if (!isRecord(value.providers))
228
+ throw new Error('providers.json.providers must be an object');
229
+ const providers = {};
230
+ for (const [providerId, provider] of Object.entries(value.providers)) {
231
+ if (!providerId.trim())
232
+ throw new Error('provider id cannot be empty');
233
+ assertSafeRegistrySegment(providerId, 'provider id');
234
+ providers[providerId] = providerConfig(provider, `providers.${providerId}`);
235
+ }
236
+ return {
237
+ ...(typeof value.$schema_version === 'number' ? { $schema_version: value.$schema_version } : {}),
238
+ ...(typeof value.nextId === 'number' ? { nextId: value.nextId } : {}),
239
+ providers,
240
+ };
241
+ }
242
+ export function resolveProviderService(providerId, serviceType, config) {
243
+ assertSafeRegistrySegment(providerId, 'provider id');
244
+ assertSafeRegistrySegment(serviceType, 'service type');
245
+ const providers = config.providers;
246
+ const provider = Object.hasOwn(providers, providerId) ? providers[providerId] : undefined;
247
+ if (!provider)
248
+ throw new Error(`Provider not found: ${providerId}`);
249
+ const service = provider.services && Object.hasOwn(provider.services, serviceType) ? provider.services[serviceType] : undefined;
250
+ if (!service)
251
+ throw new Error(`Provider ${providerId} has no service: ${serviceType}`);
252
+ const fixedStyle = Object.hasOwn(FIXED_STYLES, serviceType) ? FIXED_STYLES[serviceType] : undefined;
253
+ const style = fixedStyle || service.style || 'openai-compatible';
254
+ if (fixedStyle && service.style && service.style !== fixedStyle) {
255
+ throw new Error(`Provider ${providerId} service ${serviceType} must use style ${fixedStyle}`);
256
+ }
257
+ const resolvedBaseUrl = resolveProviderEnvironmentValue(providerId, 'baseUrl', provider.common.baseUrl);
258
+ if (!validProviderBaseUrl(resolvedBaseUrl)) {
259
+ throw new Error(`Provider ${providerId} common.baseUrl must resolve to a valid http(s) URL`);
260
+ }
261
+ const base = resolvedBaseUrl.endsWith('/') ? resolvedBaseUrl : `${resolvedBaseUrl}/`;
262
+ const parsed = new URL(service.path || '.', base);
263
+ if (parsed.origin !== new URL(base).origin)
264
+ throw new Error(`Provider ${providerId} path must stay on the common origin`);
265
+ const finalUrl = parsed.toString().replace(/\/+$/u, '');
266
+ const catalogPath = new URL(finalUrl).pathname.replace(/\/+$/u, '');
267
+ const apiKey = provider.common.apiKey === undefined
268
+ ? undefined
269
+ : resolveProviderEnvironmentValue(providerId, 'apiKey', provider.common.apiKey);
270
+ return {
271
+ providerId,
272
+ serviceType,
273
+ style,
274
+ baseUrl: finalUrl,
275
+ catalogPath,
276
+ apiKey,
277
+ headers: provider.common.headers,
278
+ queryParams: provider.common.queryParams,
279
+ modelOverrides: service.models ?? {},
280
+ };
281
+ }
282
+ export function resolveProviderServiceForBaseagent(providerId, baseagent, preferredServiceType, config) {
283
+ const serviceType = resolveProviderServiceTypeForBaseagent(baseagent, preferredServiceType);
284
+ return resolveProviderService(providerId, serviceType, config);
285
+ }
286
+ export function resolveProviderModelOverrides(providerId, serviceType, config) {
287
+ assertSafeRegistrySegment(providerId, 'provider id');
288
+ assertSafeRegistrySegment(serviceType, 'service type');
289
+ const provider = Object.hasOwn(config.providers, providerId) ? config.providers[providerId] : undefined;
290
+ if (!provider)
291
+ throw new Error(`Provider not found: ${providerId}`);
292
+ const service = provider.services && Object.hasOwn(provider.services, serviceType) ? provider.services[serviceType] : undefined;
293
+ if (!service)
294
+ throw new Error(`Provider ${providerId} has no service: ${serviceType}`);
295
+ const fixedStyle = Object.hasOwn(FIXED_STYLES, serviceType) ? FIXED_STYLES[serviceType] : undefined;
296
+ if (fixedStyle && service.style && service.style !== fixedStyle) {
297
+ throw new Error(`Provider ${providerId} service ${serviceType} must use style ${fixedStyle}`);
298
+ }
299
+ return service.models ?? {};
300
+ }
301
+ export function providerIsConfigured(config) {
302
+ return config?.provider !== undefined;
303
+ }
304
+ export function resolveServiceSelection(selections, serviceType, resolve) {
305
+ const selection = selections?.[serviceType];
306
+ if (!selection)
307
+ return undefined;
308
+ const providerId = nonEmpty(selection.provider, `Services.${serviceType}.provider`);
309
+ const model = nonEmpty(selection.model, `Services.${serviceType}.model`);
310
+ const resolved = resolve(providerId, providerServiceTypeForPurpose(serviceType));
311
+ if (resolved.modelOverrides[model]?.enabled === false)
312
+ throw new Error(`Model ${model} is disabled for provider ${providerId}`);
313
+ return { ...resolved, model, effort: selection.effort };
314
+ }
315
+ export function assertProviderSelectionWrite(next, previous, inherited, resolveModelOverrides) {
316
+ for (const [baseagent, block] of Object.entries(next.baseagents ?? {})) {
317
+ if (!isRecord(block))
318
+ continue;
319
+ const before = previous.baseagents?.[baseagent] ?? {};
320
+ const selection = { ...inherited.baseagents?.[baseagent], ...block };
321
+ for (const field of ['apiKey', 'baseUrl', 'headers', 'queryParams']) {
322
+ if (block[field] !== undefined && !isDeepStrictEqual(block[field], before[field])) {
323
+ throw new Error(`baseagents.${baseagent}.${field} is read-only legacy configuration; configure connection parameters in providers.json`);
324
+ }
325
+ }
326
+ const changed = !isDeepStrictEqual(block.provider, before.provider)
327
+ || !isDeepStrictEqual(block.providerServiceType, before.providerServiceType)
328
+ || !isDeepStrictEqual(block.model, before.model) || !isDeepStrictEqual(block.fallback, before.fallback);
329
+ if (changed) {
330
+ const serviceType = resolveProviderServiceTypeForBaseagent(baseagent, selection.providerServiceType);
331
+ if (selection.provider !== undefined) {
332
+ const modelOverrides = resolveModelOverrides(selection.provider, serviceType);
333
+ if (selection.model && modelOverrides[selection.model]?.enabled === false) {
334
+ throw new Error(`Model ${selection.model} is disabled for provider ${selection.provider}`);
335
+ }
336
+ }
337
+ for (const candidate of Array.isArray(selection.fallback) ? selection.fallback : []) {
338
+ const providerId = candidate?.provider ?? selection.provider;
339
+ if (!providerId || typeof candidate?.model !== 'string')
340
+ continue;
341
+ const modelOverrides = resolveModelOverrides(providerId, serviceType);
342
+ if (modelOverrides[candidate.model]?.enabled === false) {
343
+ throw new Error(`Model ${candidate.model} is disabled for provider ${providerId}`);
344
+ }
345
+ }
346
+ }
347
+ }
348
+ for (const field of ['apikey', 'base_url', 'headers', 'queryParams']) {
349
+ const value = next.decision_model?.[field];
350
+ if (value !== undefined && !isDeepStrictEqual(value, previous.decision_model?.[field])) {
351
+ throw new Error(`decision_model.${field} is read-only legacy configuration; configure Services.decision and providers.json`);
352
+ }
353
+ }
354
+ if (next.Services !== undefined && !isRecord(next.Services))
355
+ throw new Error('Services must be an object');
356
+ for (const [serviceType, selection] of Object.entries(next.Services ?? {})) {
357
+ assertSafeRegistrySegment(serviceType, 'service type');
358
+ if (!isRecord(selection))
359
+ throw new Error(`Services.${serviceType} must be an object`);
360
+ assertFields(selection, ['provider', 'model', 'effort', 'fallback'], `Services.${serviceType}`);
361
+ if (isDeepStrictEqual(selection, previous.Services?.[serviceType]))
362
+ continue;
363
+ const effective = { ...inherited.Services?.[serviceType], ...selection };
364
+ if (!Object.keys(effective).length)
365
+ continue;
366
+ const providerId = nonEmpty(effective.provider, `Services.${serviceType}.provider`);
367
+ nonEmpty(effective.model, `Services.${serviceType}.model`);
368
+ const modelOverrides = resolveModelOverrides(providerId, providerServiceTypeForPurpose(serviceType));
369
+ if (modelOverrides[effective.model]?.enabled === false) {
370
+ throw new Error(`Model ${effective.model} is disabled for provider ${providerId}`);
371
+ }
372
+ for (const candidate of Array.isArray(effective.fallback) ? effective.fallback : []) {
373
+ const providerId = candidate?.provider ?? effective.provider;
374
+ if (!providerId || typeof candidate?.model !== 'string')
375
+ continue;
376
+ const candidateOverrides = resolveModelOverrides(providerId, providerServiceTypeForPurpose(serviceType));
377
+ if (candidateOverrides[candidate.model]?.enabled === false) {
378
+ throw new Error(`Model ${candidate.model} is disabled for provider ${providerId}`);
379
+ }
380
+ }
381
+ }
382
+ }
@@ -0,0 +1,74 @@
1
+ import { DEFAULT_ECAGENT_BASE_URL, resolveAnthropicConfig, resolveEcagentConfig, resolveGoogleConfig, resolveOpenaiConfig, } from '../agents/baseagent.js';
2
+ import { ConfigTarget, read } from './config-manager.js';
3
+ import { readProvidersFile } from './providers.js';
4
+ import { providerNameFromBaseUrlIfStatic, providerServiceTypeForBaseagent, resolveProviderServiceForBaseagent, validateProvidersFile } from './providers-config.js';
5
+ const IMPORTABLE_BASEAGENTS = ['claude', 'codex', 'gemini', 'ecagent'];
6
+ function resolveConnection(baseagent, config, projectPath) {
7
+ if (baseagent === 'claude')
8
+ return resolveAnthropicConfig(config, config.agents?.claude, { projectPath });
9
+ if (baseagent === 'codex')
10
+ return resolveOpenaiConfig(config, config.agents?.codex);
11
+ if (baseagent === 'ecagent')
12
+ return resolveEcagentConfig(config, config.agents?.ecagent);
13
+ return {
14
+ ...resolveGoogleConfig(config, { ...config.agents?.gemini, cliPath: config.agents?.gemini?.cliPath || 'gemini' }),
15
+ baseUrl: process.env.GOOGLE_GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta',
16
+ };
17
+ }
18
+ export function providerFromBaseagent(baseagent) {
19
+ if (!IMPORTABLE_BASEAGENTS.includes(baseagent))
20
+ throw new Error('Invalid baseagent');
21
+ const source = read(ConfigTarget.Defaults, undefined, { cache: false, expand: true });
22
+ const selected = baseagent;
23
+ const block = source?.baseagents?.[selected];
24
+ if (block?.provider !== undefined) {
25
+ const providers = readProvidersFile().providers;
26
+ const provider = Object.hasOwn(providers, block.provider) ? providers[block.provider] : undefined;
27
+ if (!provider)
28
+ throw new Error('Source baseagent references a missing Provider');
29
+ const resolved = resolveProviderServiceForBaseagent(block.provider, selected, block.providerServiceType, { providers });
30
+ const serviceType = resolved.serviceType;
31
+ const service = provider.services?.[serviceType];
32
+ if (!service)
33
+ throw new Error('Source Provider does not support the selected baseagent');
34
+ if (!provider.common.apiKey?.trim())
35
+ throw new Error('Source baseagent has no API key / token');
36
+ return structuredClone({ name: provider.name || providerNameFromBaseUrlIfStatic(provider.common.baseUrl), common: provider.common, services: { [serviceType]: service } });
37
+ }
38
+ const serviceType = providerServiceTypeForBaseagent(selected);
39
+ const config = { agents: source?.baseagents };
40
+ const connection = resolveConnection(selected, config, source?.projects?.defaultPath);
41
+ if (connection.useVertex)
42
+ throw new Error('Vertex AI settings cannot be imported as a Gemini API-key Provider');
43
+ if (!connection.apiKey?.trim() || /your-|placeholder|<redacted>/u.test(connection.apiKey))
44
+ throw new Error('Source baseagent has no valid API key / token');
45
+ const defaultBaseUrls = {
46
+ claude: 'https://api.anthropic.com',
47
+ codex: 'https://api.openai.com/v1',
48
+ gemini: 'https://generativelanguage.googleapis.com/v1beta',
49
+ ecagent: DEFAULT_ECAGENT_BASE_URL,
50
+ };
51
+ const provider = {
52
+ common: {
53
+ baseUrl: connection.baseUrl || defaultBaseUrls[selected],
54
+ ...(connection.apiKey ? { apiKey: connection.apiKey } : {}),
55
+ ...(connection.headers ? { headers: connection.headers } : {}),
56
+ ...(connection.queryParams ? { queryParams: connection.queryParams } : {}),
57
+ },
58
+ services: { [serviceType]: { path: '' } },
59
+ };
60
+ const normalized = validateProvidersFile({ providers: { imported: provider } }).providers.imported;
61
+ normalized.name ||= providerNameFromBaseUrlIfStatic(normalized.common.baseUrl);
62
+ return normalized;
63
+ }
64
+ export function listBaseagentImportSources() {
65
+ return IMPORTABLE_BASEAGENTS.flatMap(baseagent => {
66
+ try {
67
+ const provider = providerFromBaseagent(baseagent);
68
+ return [{ baseagent, name: provider.name || baseagent, baseUrl: provider.common.baseUrl }];
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ });
74
+ }