Skip to content

Commit 6df7cb3

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
feat(oracle-fusion): add SCM integration
1 parent b0d41e7 commit 6df7cb3

50 files changed

Lines changed: 5343 additions & 9 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/docs/components/ui/icon-mapping.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ import {
175175
OktaIcon,
176176
OnePasswordIcon,
177177
OpenAIIcon,
178+
OracleIcon,
178179
OutlookIcon,
179180
PackageSearchIcon,
180181
PagerDutyIcon,
@@ -480,6 +481,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
480481
onedrive: MicrosoftOneDriveIcon,
481482
onepassword: OnePasswordIcon,
482483
openai: OpenAIIcon,
484+
oracle_fusion_scm: OracleIcon,
483485
outlook: OutlookIcon,
484486
pagerduty: PagerDutyIcon,
485487
parallel_ai: ParallelIcon,

‎apps/docs/content/docs/integrations/meta.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
"onedrive",
188188
"onepassword",
189189
"openai",
190+
"oracle_fusion_scm",
190191
"outlook",
191192
"pagerduty",
192193
"parallel_ai",

‎apps/docs/content/docs/integrations/oracle_fusion_scm.mdx‎

Lines changed: 1009 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { OracleFusionScmBlock, OracleFusionScmBlockMeta } from '@/blocks/blocks/oracle_fusion_scm'
6+
import type { SubBlockConfig } from '@/blocks/types'
7+
import * as oracleFusionScmTools from '@/tools/oracle_fusion_scm'
8+
import type { ToolConfig } from '@/tools/types'
9+
10+
const toolsById = new Map<string, ToolConfig>(
11+
Object.values(oracleFusionScmTools)
12+
.filter((value): value is ToolConfig =>
13+
Boolean(value && typeof value === 'object' && 'id' in value)
14+
)
15+
.map((tool) => [tool.id, tool])
16+
)
17+
18+
const subBlocks: SubBlockConfig[] = OracleFusionScmBlock.subBlocks
19+
const access = OracleFusionScmBlock.tools.access ?? []
20+
const operation = subBlocks.find((subBlock) => subBlock.id === 'operation')
21+
const options =
22+
typeof operation?.options === 'function' ? operation.options() : (operation?.options ?? [])
23+
const operationIds = options.map((option) => option.id)
24+
25+
function visibleFor(operationId: string): SubBlockConfig[] {
26+
return subBlocks.filter((subBlock) => {
27+
const condition = subBlock.condition
28+
if (typeof condition === 'function' || !condition) return true
29+
if (condition.field !== 'operation') return false
30+
return Array.isArray(condition.value)
31+
? condition.value.includes(operationId)
32+
: condition.value === operationId
33+
})
34+
}
35+
36+
function visibleOutputNames(operationId: string): string[] {
37+
return Object.entries(OracleFusionScmBlock.outputs)
38+
.filter(([, output]) => {
39+
const condition = output.condition
40+
if (!condition) return true
41+
return Array.isArray(condition.value)
42+
? condition.value.includes(operationId)
43+
: condition.value === operationId
44+
})
45+
.map(([name]) => name)
46+
}
47+
48+
describe('Oracle Fusion SCM block', () => {
49+
it('exposes all 20 tools as direct operation values and canvas sentences', () => {
50+
expect(access).toHaveLength(20)
51+
expect(new Set(operationIds)).toEqual(new Set(access))
52+
expect(new Set(toolsById.keys())).toEqual(new Set(access))
53+
expect(
54+
Object.keys(OracleFusionScmBlock.canvasPresentation?.sentences?.byOperation ?? {})
55+
).toEqual(expect.arrayContaining(access))
56+
for (const operationId of access) {
57+
expect(OracleFusionScmBlock.tools.config?.tool?.({ operation: operationId })).toBe(
58+
operationId
59+
)
60+
}
61+
})
62+
63+
it('keeps subBlock ids unique and covers every required tool parameter', () => {
64+
const ids = subBlocks.map((subBlock) => subBlock.id)
65+
expect(new Set(ids).size).toBe(ids.length)
66+
67+
const missing: string[] = []
68+
for (const operationId of access) {
69+
const tool = toolsById.get(operationId)
70+
if (!tool) continue
71+
const visible = visibleFor(operationId)
72+
const canonicalIds = new Set(
73+
visible.flatMap((subBlock) => [subBlock.id, subBlock.canonicalParamId].filter(Boolean))
74+
)
75+
for (const [name, param] of Object.entries(tool.params)) {
76+
if (param.visibility === 'hidden' || !param.required) continue
77+
if (!canonicalIds.has(name)) missing.push(`${operationId}.${name}`)
78+
}
79+
}
80+
expect(missing).toEqual([])
81+
})
82+
83+
it('shows finder only for the four documented resource families', () => {
84+
const finder = subBlocks.find((subBlock) => subBlock.id === 'finder')
85+
expect(finder?.condition).toEqual({
86+
field: 'operation',
87+
value: [
88+
'oracle_fusion_scm_list_inventory_organizations',
89+
'oracle_fusion_scm_list_items',
90+
'oracle_fusion_scm_list_inventory_transactions',
91+
'oracle_fusion_scm_list_supply_requests',
92+
],
93+
})
94+
})
95+
96+
it('shows pagination only for lists and only the matching wrapper for details', () => {
97+
const pageOutputs = [
98+
'items',
99+
'count',
100+
'hasMore',
101+
'limit',
102+
'offset',
103+
'totalResults',
104+
'nextOffset',
105+
]
106+
for (const operationId of access.filter((id) => id.includes('_list_'))) {
107+
expect(visibleOutputNames(operationId)).toEqual(pageOutputs)
108+
}
109+
expect(visibleOutputNames('oracle_fusion_scm_get_inventory_organization')).toEqual([
110+
'organization',
111+
])
112+
expect(visibleOutputNames('oracle_fusion_scm_get_item')).toEqual(['item'])
113+
expect(visibleOutputNames('oracle_fusion_scm_get_on_hand_quantity')).toEqual(['onHandQuantity'])
114+
expect(visibleOutputNames('oracle_fusion_scm_get_inventory_transaction')).toEqual([
115+
'transaction',
116+
])
117+
expect(visibleOutputNames('oracle_fusion_scm_get_supply_request')).toEqual(['supplyRequest'])
118+
expect(visibleOutputNames('oracle_fusion_scm_get_supply_order_line')).toEqual([
119+
'supplyOrderLine',
120+
])
121+
expect(visibleOutputNames('oracle_fusion_scm_get_shipment')).toEqual(['shipment'])
122+
expect(visibleOutputNames('oracle_fusion_scm_get_shipment_line')).toEqual(['shipmentLine'])
123+
expect(visibleOutputNames('oracle_fusion_scm_get_manufacturing_work_order')).toEqual([
124+
'manufacturingWorkOrder',
125+
])
126+
expect(visibleOutputNames('oracle_fusion_scm_get_maintenance_work_order')).toEqual([
127+
'maintenanceWorkOrder',
128+
])
129+
})
130+
131+
it('binds all six project selectors to the stored SCM credential', () => {
132+
const selectors = subBlocks.filter((subBlock) => subBlock.type === 'project-selector')
133+
expect(selectors).toHaveLength(6)
134+
for (const selector of selectors) {
135+
expect(selector.serviceId).toBe('oracle_fusion_scm')
136+
expect(selector.dependsOn).toEqual(['credential'])
137+
expect(selector.canonicalParamId).toBeTruthy()
138+
}
139+
})
140+
141+
it('ships exactly nine templates and operation-grounded skills', () => {
142+
expect(OracleFusionScmBlockMeta.templates).toHaveLength(9)
143+
expect(OracleFusionScmBlockMeta.skills).toHaveLength(7)
144+
for (const template of OracleFusionScmBlockMeta.templates) {
145+
expect(template.prompt.length).toBeGreaterThan(40)
146+
expect(template.modules.length).toBeGreaterThan(0)
147+
expect(template.tags.length).toBeGreaterThan(0)
148+
}
149+
for (const skill of OracleFusionScmBlockMeta.skills) {
150+
expect(skill.name).toMatch(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
151+
expect(skill.content).toContain('# ')
152+
expect(skill.content).toContain('## Steps')
153+
expect(skill.content).toContain('## Output')
154+
}
155+
})
156+
})

0 commit comments

Comments
 (0)