@elevasis/core 0.45.0 → 0.47.0

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.
@@ -26,10 +26,15 @@ export type OpenRouterModel = 'openrouter/z-ai/glm-5'
26
26
  */
27
27
  export type GoogleModel = 'gemini-3-flash-preview' | 'gemini-3.1-flash-lite-preview'
28
28
 
29
- /**
30
- * Supported Anthropic models (direct SDK access via @anthropic-ai/sdk)
31
- */
32
- export type AnthropicModel = 'claude-sonnet-4-5'
29
+ /**
30
+ * Supported Anthropic models (direct SDK access via @anthropic-ai/sdk)
31
+ */
32
+ export type AnthropicModel =
33
+ | 'claude-opus-4-8'
34
+ | 'claude-sonnet-4-6'
35
+ | 'claude-haiku-4-5-20251001'
36
+ | 'claude-haiku-4-5'
37
+ | 'claude-sonnet-4-5'
33
38
 
34
39
  /** Supported LLM models */
35
40
  export type LLMModel = OpenAIModel | OpenRouterModel | GoogleModel | AnthropicModel | 'mock'
@@ -122,26 +127,48 @@ export const GoogleConfigSchema = z.object({
122
127
  modelOptions: GoogleOptionsSchema.optional()
123
128
  })
124
129
 
125
- /**
126
- * Anthropic model options schema
127
- * Currently empty - future options: budget_tokens for extended thinking
128
- */
129
- const AnthropicOptionsSchema = z.object({})
130
-
131
- /**
132
- * Anthropic config schema
133
- * Validates ModelConfig for Anthropic provider (direct SDK access)
134
- */
135
- export const AnthropicConfigSchema = z.object({
136
- model: z.enum(['claude-sonnet-4-5']),
137
- provider: z.literal('anthropic'),
138
- apiKey: z.string(),
139
- temperature: z.number().min(0).max(1).optional(),
140
- maxOutputTokens: z.number().min(1000).optional(), // Anthropic requires max_tokens
141
- topP: z.number().min(0).max(1).optional(),
142
- modelOptions: AnthropicOptionsSchema.optional()
143
- })
130
+ /**
131
+ * Anthropic model options schema
132
+ * Currently empty - future options must be added per supported model family
133
+ */
134
+ const AnthropicOptionsSchema = z.object({}).strict()
144
135
 
136
+ /**
137
+ * Anthropic config schema
138
+ * Validates ModelConfig for Anthropic provider (direct SDK access)
139
+ */
140
+ const AnthropicStandardConfigSchema = z.object({
141
+ model: z.enum([
142
+ 'claude-sonnet-4-6',
143
+ 'claude-haiku-4-5-20251001',
144
+ 'claude-haiku-4-5',
145
+ 'claude-sonnet-4-5'
146
+ ]),
147
+ provider: z.literal('anthropic'),
148
+ apiKey: z.string(),
149
+ temperature: z.number().min(0).max(1).optional(),
150
+ maxOutputTokens: z.number().min(1000).max(64000).optional(), // Anthropic requires max_tokens
151
+ topP: z.number().min(0).max(1).optional(),
152
+ modelOptions: AnthropicOptionsSchema.optional()
153
+ })
154
+
155
+ const AnthropicOpus48ConfigSchema = z.object({
156
+ model: z.literal('claude-opus-4-8'),
157
+ provider: z.literal('anthropic'),
158
+ apiKey: z.string(),
159
+ temperature: z.literal(1).optional(),
160
+ maxOutputTokens: z.number().min(1000).max(128000).optional(), // Anthropic requires max_tokens
161
+ topP: z.literal(1).optional(),
162
+ topK: z.undefined().optional(),
163
+ top_k: z.undefined().optional(),
164
+ modelOptions: AnthropicOptionsSchema.optional()
165
+ })
166
+
167
+ export const AnthropicConfigSchema = z.discriminatedUnion('model', [
168
+ AnthropicOpus48ConfigSchema,
169
+ AnthropicStandardConfigSchema
170
+ ])
171
+
145
172
  /**
146
173
  * Infer TypeScript types from schemas
147
174
  */
@@ -191,16 +218,18 @@ export interface ModelInfo {
191
218
  minTokens: number
192
219
  /** Recommended tokens for production use (typically 2x minimum) */
193
220
  recommendedTokens: number
194
- /** Maximum context window size (total tokens: input + output) */
195
- maxTokens: number
196
- /** Model category for grouping and documentation */
197
- category: 'reasoning' | 'standard' | 'embedding'
198
- /** Zod schema for validating complete ModelConfig (replaces constraints + optionsSchema) */
199
- configSchema?: z.ZodType<ModelConfig>
200
- }
221
+ /** Maximum context window size (total tokens: input + output) */
222
+ maxTokens: number
223
+ /** Maximum output tokens supported by the synchronous generation API */
224
+ maxOutputTokens?: number
225
+ /** Model category for grouping and documentation */
226
+ category: 'reasoning' | 'standard' | 'embedding'
227
+ /** Zod schema for validating complete ModelConfig (replaces constraints + optionsSchema) */
228
+ configSchema?: z.ZodType<ModelConfig>
229
+ }
201
230
 
202
- // Model configuration as of 2026-03-17
203
- export const MODEL_INFO: Record<LLMModel, ModelInfo> = {
231
+ // Model configuration as of 2026-06-09
232
+ export const MODEL_INFO: Record<LLMModel, ModelInfo> = {
204
233
  // OpenAI GPT-5 (Reasoning Models)
205
234
  'gpt-5': {
206
235
  inputCostPer1M: 125, // $1.25 per 1M tokens
@@ -268,17 +297,58 @@ export const MODEL_INFO: Record<LLMModel, ModelInfo> = {
268
297
  category: 'standard',
269
298
  configSchema: GoogleConfigSchema
270
299
  },
271
- // Anthropic Claude Models (direct SDK access via @anthropic-ai/sdk)
272
- 'claude-sonnet-4-5': {
273
- inputCostPer1M: 300, // $3.00 per 1M tokens
274
- outputCostPer1M: 1500, // $15.00 per 1M tokens
275
- minTokens: 4000,
276
- recommendedTokens: 8000,
277
- maxTokens: 200000, // 200k context window
278
- category: 'standard',
279
- configSchema: AnthropicConfigSchema
280
- }
281
- }
300
+ // Anthropic Claude Models (direct SDK access via @anthropic-ai/sdk)
301
+ 'claude-opus-4-8': {
302
+ inputCostPer1M: 500, // $5.00 per 1M tokens
303
+ outputCostPer1M: 2500, // $25.00 per 1M tokens
304
+ minTokens: 4000,
305
+ recommendedTokens: 8000,
306
+ maxTokens: 1000000, // 1M context window
307
+ maxOutputTokens: 128000,
308
+ category: 'reasoning',
309
+ configSchema: AnthropicConfigSchema
310
+ },
311
+ 'claude-sonnet-4-6': {
312
+ inputCostPer1M: 300, // $3.00 per 1M tokens
313
+ outputCostPer1M: 1500, // $15.00 per 1M tokens
314
+ minTokens: 4000,
315
+ recommendedTokens: 8000,
316
+ maxTokens: 1000000, // 1M context window
317
+ maxOutputTokens: 64000,
318
+ category: 'standard',
319
+ configSchema: AnthropicConfigSchema
320
+ },
321
+ 'claude-haiku-4-5-20251001': {
322
+ inputCostPer1M: 100, // $1.00 per 1M tokens
323
+ outputCostPer1M: 500, // $5.00 per 1M tokens
324
+ minTokens: 4000,
325
+ recommendedTokens: 8000,
326
+ maxTokens: 200000, // 200k context window
327
+ maxOutputTokens: 64000,
328
+ category: 'standard',
329
+ configSchema: AnthropicConfigSchema
330
+ },
331
+ 'claude-haiku-4-5': {
332
+ inputCostPer1M: 100, // $1.00 per 1M tokens
333
+ outputCostPer1M: 500, // $5.00 per 1M tokens
334
+ minTokens: 4000,
335
+ recommendedTokens: 8000,
336
+ maxTokens: 200000, // 200k context window
337
+ maxOutputTokens: 64000,
338
+ category: 'standard',
339
+ configSchema: AnthropicConfigSchema
340
+ },
341
+ 'claude-sonnet-4-5': {
342
+ inputCostPer1M: 300, // $3.00 per 1M tokens
343
+ outputCostPer1M: 1500, // $15.00 per 1M tokens
344
+ minTokens: 4000,
345
+ recommendedTokens: 8000,
346
+ maxTokens: 200000, // 200k context window
347
+ maxOutputTokens: 64000,
348
+ category: 'standard',
349
+ configSchema: AnthropicConfigSchema
350
+ }
351
+ }
282
352
 
283
353
  /**
284
354
  * Get model information (pricing and token requirements)
@@ -1097,3 +1097,177 @@ describe('formatOmDescribe', () => {
1097
1097
  expect(output).toContain('System: sales.lead-gen')
1098
1098
  })
1099
1099
  })
1100
+
1101
+ // ---------------------------------------------------------------------------
1102
+ // Domain-item resolution (item:<domain>:<id> + bare-id back-compat)
1103
+ // ---------------------------------------------------------------------------
1104
+
1105
+ /**
1106
+ * Fixture exercising all six graph-projected domain-item kinds.
1107
+ * `knowledge.brand-guide` governs the client via a `client:client-1` link.
1108
+ */
1109
+ const DOMAIN_ITEM_MODEL = {
1110
+ knowledge: {
1111
+ 'knowledge.brand-guide': {
1112
+ id: 'knowledge.brand-guide',
1113
+ kind: 'reference',
1114
+ title: 'Brand Guide',
1115
+ summary: 'Branding rules for the client.',
1116
+ body: '## Brand Guide\n\nVoice and palette.',
1117
+ links: [{ nodeId: 'client:client-1' }],
1118
+ ownerIds: [],
1119
+ updatedAt: '2026-01-01'
1120
+ }
1121
+ },
1122
+ clients: {
1123
+ 'client-1': {
1124
+ id: 'client-1',
1125
+ name: 'Byron for Irvine',
1126
+ organizationName: 'Byron for Irvine Campaign',
1127
+ shortName: 'Byron',
1128
+ clientBrief: 'Local city-council campaign.',
1129
+ candidateName: 'Byron'
1130
+ }
1131
+ },
1132
+ roles: {
1133
+ 'role.ops-lead': {
1134
+ id: 'role.ops-lead',
1135
+ order: 10,
1136
+ title: 'Ops Lead',
1137
+ responsibilities: ['Run operations.'],
1138
+ responsibleFor: []
1139
+ }
1140
+ },
1141
+ policies: {
1142
+ 'policy.review-spend': {
1143
+ id: 'policy.review-spend',
1144
+ order: 10,
1145
+ label: 'Review Spend',
1146
+ description: 'Review spend over threshold.'
1147
+ }
1148
+ },
1149
+ customers: {
1150
+ 'cust-1': {
1151
+ id: 'cust-1',
1152
+ name: 'SMB Automation Segment',
1153
+ description: 'Small businesses adopting automation.'
1154
+ }
1155
+ },
1156
+ offerings: {
1157
+ 'off-1': {
1158
+ id: 'off-1',
1159
+ name: 'Automation Starter Package',
1160
+ description: 'Entry automation engagement.'
1161
+ }
1162
+ },
1163
+ goals: {
1164
+ 'goal-1': {
1165
+ id: 'goal-1',
1166
+ description: 'Double recurring revenue this year.'
1167
+ }
1168
+ }
1169
+ } as unknown as OrganizationModel
1170
+
1171
+ describe('omDescribe — domain items', () => {
1172
+ it('resolves item:clients:<id> to the full client profile + location + governing knowledge', () => {
1173
+ const result = omDescribe(DOMAIN_ITEM_MODEL, 'item:clients:client-1')
1174
+ expect(result).toBeDefined()
1175
+ if (!result || result.kind !== 'domain-item') throw new Error('expected domain-item result')
1176
+ expect(result.domain).toBe('clients')
1177
+ expect(result.id).toBe('item:clients:client-1')
1178
+ expect(result.name).toBe('Byron for Irvine')
1179
+ // Full ClientProfile payload is carried in `record`.
1180
+ expect(result.record).toMatchObject({
1181
+ organizationName: 'Byron for Irvine Campaign',
1182
+ candidateName: 'Byron'
1183
+ })
1184
+ // Location.
1185
+ expect(result.location.domainLabel).toBe('Clients')
1186
+ expect(result.location.graphNodeId).toBe('client:client-1')
1187
+ expect(result.location.containsParentIds).toContain('organization-model')
1188
+ // Governing knowledge.
1189
+ expect(result.governingKnowledge.map((g) => g.id)).toContain('knowledge.brand-guide')
1190
+ })
1191
+
1192
+ it('resolves bare client:<id> identically to the item: form (back-compat)', () => {
1193
+ const bare = omDescribe(DOMAIN_ITEM_MODEL, 'client:client-1')
1194
+ const canonical = omDescribe(DOMAIN_ITEM_MODEL, 'item:clients:client-1')
1195
+ expect(bare).toEqual(canonical)
1196
+ })
1197
+
1198
+ it('resolves the other five domain kinds via item: and bare ids', () => {
1199
+ const cases: Array<{ item: string; bare: string; domain: string; name: string }> = [
1200
+ { item: 'item:roles:role.ops-lead', bare: 'role:role.ops-lead', domain: 'roles', name: 'Ops Lead' },
1201
+ {
1202
+ item: 'item:policies:policy.review-spend',
1203
+ bare: 'policy:policy.review-spend',
1204
+ domain: 'policies',
1205
+ name: 'Review Spend'
1206
+ },
1207
+ { item: 'item:customers:cust-1', bare: 'customer:cust-1', domain: 'customers', name: 'SMB Automation Segment' },
1208
+ { item: 'item:offerings:off-1', bare: 'offering:off-1', domain: 'offerings', name: 'Automation Starter Package' },
1209
+ { item: 'item:goals:goal-1', bare: 'goal:goal-1', domain: 'goals', name: 'Double recurring revenue this year.' }
1210
+ ]
1211
+ for (const c of cases) {
1212
+ const viaItem = omDescribe(DOMAIN_ITEM_MODEL, c.item)
1213
+ expect(viaItem, `item form ${c.item}`).toBeDefined()
1214
+ if (!viaItem || viaItem.kind !== 'domain-item') throw new Error(`expected domain-item for ${c.item}`)
1215
+ expect(viaItem.domain).toBe(c.domain)
1216
+ expect(viaItem.name).toBe(c.name)
1217
+ // Bare form resolves to the same result.
1218
+ expect(omDescribe(DOMAIN_ITEM_MODEL, c.bare)).toEqual(viaItem)
1219
+ }
1220
+ })
1221
+
1222
+ it('returns undefined for an unknown item id', () => {
1223
+ expect(omDescribe(DOMAIN_ITEM_MODEL, 'item:clients:does-not-exist')).toBeUndefined()
1224
+ expect(omDescribe(DOMAIN_ITEM_MODEL, 'client:does-not-exist')).toBeUndefined()
1225
+ })
1226
+
1227
+ it('returns undefined for an item ref with an unknown domain', () => {
1228
+ expect(omDescribe(DOMAIN_ITEM_MODEL, 'item:widgets:thing-1')).toBeUndefined()
1229
+ })
1230
+ })
1231
+
1232
+ describe('parsePath — domain-item mounts', () => {
1233
+ it('parses /by-domain/<domain> into a by-domain mount', () => {
1234
+ expect(parsePath('/by-domain/clients')).toEqual({ mount: 'by-domain', args: ['clients'] })
1235
+ })
1236
+
1237
+ it('parses /by-item/<domain>/<itemId> into a by-item mount', () => {
1238
+ expect(parsePath('/by-item/clients/client-1')).toEqual({ mount: 'by-item', args: ['clients', 'client-1'] })
1239
+ })
1240
+
1241
+ it('throws on an unknown domain for /by-domain', () => {
1242
+ expect(() => parsePath('/by-domain/widgets')).toThrow(/unknown domain/i)
1243
+ })
1244
+
1245
+ it('throws on an unknown domain for /by-item', () => {
1246
+ expect(() => parsePath('/by-item/widgets/thing-1')).toThrow(/unknown domain/i)
1247
+ })
1248
+
1249
+ it('throws when /by-item is missing the itemId', () => {
1250
+ expect(() => parsePath('/by-item/clients')).toThrow(/by-item/i)
1251
+ })
1252
+ })
1253
+
1254
+ describe('formatOmDescribe — domain items', () => {
1255
+ it('renders a domain-item header, location, record, and governing knowledge', () => {
1256
+ const result = omDescribe(DOMAIN_ITEM_MODEL, 'item:clients:client-1')
1257
+ const output = formatOmDescribe(result)
1258
+ expect(output).toContain('Domain Item: item:clients:client-1')
1259
+ expect(output).toContain('Domain: Clients')
1260
+ expect(output).toContain('Name: Byron for Irvine')
1261
+ expect(output).toContain('Graph node: client:client-1')
1262
+ expect(output).toContain('Governing knowledge:')
1263
+ expect(output).toContain('knowledge.brand-guide')
1264
+ })
1265
+
1266
+ it('serializes the domain-item result directly via JSON.stringify', () => {
1267
+ const result = omDescribe(DOMAIN_ITEM_MODEL, 'item:clients:client-1')
1268
+ const json = JSON.parse(JSON.stringify(result))
1269
+ expect(json.kind).toBe('domain-item')
1270
+ expect(json.domain).toBe('clients')
1271
+ expect(json.id).toBe('item:clients:client-1')
1272
+ })
1273
+ })
@@ -1,6 +1,7 @@
1
1
  import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
2
2
  import type {
3
3
  KnowledgeMount,
4
+ OmDescribeDomainItem,
4
5
  OmDescribeKnowledge,
5
6
  OmDescribeOntology,
6
7
  OmDescribePolicy,
@@ -170,6 +171,8 @@ export function formatOmDescribe(result: OmDescribeResult | undefined): string {
170
171
  return formatRoleDescribe(result)
171
172
  case 'policy':
172
173
  return formatPolicyDescribe(result)
174
+ case 'domain-item':
175
+ return formatDomainItemDescribe(result)
173
176
  }
174
177
  }
175
178
 
@@ -320,3 +323,39 @@ function formatPolicyDescribe(r: OmDescribePolicy): string {
320
323
 
321
324
  return join([header, effectSection, appliesSection])
322
325
  }
326
+
327
+ function formatDomainItemDescribe(r: OmDescribeDomainItem): string {
328
+ const header = join([
329
+ `Domain Item: ${r.id}`,
330
+ [
331
+ field('Domain', r.location.domainLabel),
332
+ field('Name', r.name),
333
+ field('Graph node', r.location.graphNodeId),
334
+ field('Parent nodes', r.location.containsParentIds.join(', '))
335
+ ]
336
+ .filter((s) => s.length > 0)
337
+ .join('\n')
338
+ ])
339
+
340
+ // Key record fields — emit a compact subset of the record payload.
341
+ const recordLines: string[] = []
342
+ for (const [k, v] of Object.entries(r.record)) {
343
+ if (v === undefined || v === null) continue
344
+ if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue
345
+ const display = Array.isArray(v)
346
+ ? v.length === 0
347
+ ? null
348
+ : `[${(v as unknown[]).slice(0, 5).join(', ')}${v.length > 5 ? `, ...+${v.length - 5}` : ''}]`
349
+ : typeof v === 'object'
350
+ ? JSON.stringify(v).slice(0, 120)
351
+ : String(v)
352
+ if (display === null) continue
353
+ recordLines.push(` ${k}: ${display}`)
354
+ }
355
+ const recordSection = recordLines.length ? `Record:\n${recordLines.join('\n')}` : ''
356
+
357
+ const govLines = r.governingKnowledge.map((g) => ` - ${g.id} [${g.knowledgeKind}] ${g.title}`)
358
+ const govSection = govLines.length ? `Governing knowledge:\n${govLines.join('\n')}` : 'Governing knowledge:\n (none)'
359
+
360
+ return join([header, recordSection, govSection])
361
+ }
@@ -10,7 +10,8 @@ export {
10
10
  omDescribe,
11
11
  listAllSystemsFlat,
12
12
  listAllResources,
13
- listAllRoles
13
+ listAllRoles,
14
+ DOMAIN_ITEM_KINDS
14
15
  } from './queries'
15
16
  export type {
16
17
  KnowledgeMount,
@@ -24,7 +25,11 @@ export type {
24
25
  OmDescribeKnowledge,
25
26
  OmDescribeOntology,
26
27
  OmDescribeRole,
27
- OmDescribePolicy
28
+ OmDescribePolicy,
29
+ OmDescribeDomainItem,
30
+ OmDescribeDomainItemLocation,
31
+ OmDescribeDomainItemKnowledgeRef,
32
+ DomainItemKind
28
33
  } from './queries'
29
34
 
30
35
  export { formatText, formatJson, formatIdsOnly, formatOmSearchHits, formatOmDescribe } from './format'