Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,6 @@
"prettier": "^3.5.3",
"tsx": "^4.19.3",
"typescript": "^5.8.3"
}
},
"packageManager": "pnpm@10.12.2"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's have this, or my pnpm install alwasy write new field

}
14 changes: 14 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@athena/core",
"scripts": {},
"repository": {
"type": "git",
"url": "https://github.com/Athena-AI-Lab/athena-core.git"
},
"dependencies": {
"@llama-flow/core": "^0.4.4"
},
"devDependencies": {
"vitest": "^3.2.4"
}
}
78 changes: 78 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
createWorkflow, workflowEvent
} from '@llama-flow/core'
import {
pluginRegisterEvent,
workflowAsyncContext,
Plugin,
PluginState, pluginUnregisterEvent
} from './plugin.js'

export type AthenaConfig = {
add (plugin: Plugin): AthenaConfig;
run (): Athena
}

export type Athena = {
get plugins (): ReadonlySet<Plugin>
stop (): Promise<void>
}

const haltEvent = workflowEvent()

export function createAthena (): AthenaConfig {
const coreWorkflow = createWorkflow()
const pluginSet = new Set<Plugin>()
const pluginStateMap = new WeakMap<Plugin, PluginState>()
coreWorkflow.handle([pluginRegisterEvent], (pluginRegister) => {
pluginSet.values().every((plugin) => {
const pluginState = pluginStateMap.get(plugin)!
const pluginWorkflow = pluginState.workflow
const { sendEvent } = pluginWorkflow.createContext()
sendEvent(pluginRegister)
})
return haltEvent.with()
})
coreWorkflow.handle([pluginUnregisterEvent], (pluginUnregister) => {
pluginSet.values().every((plugin) => {
const pluginState = pluginStateMap.get(plugin)!
const pluginWorkflow = pluginState.workflow
const { sendEvent } = pluginWorkflow.createContext()
sendEvent(pluginUnregister)
})
return haltEvent.with()
})

const config: AthenaConfig = {
add: (plugin: Plugin) => {
pluginSet.add(plugin)
const pluginWorkflow = createWorkflow()
const pluginState: PluginState = {
description: null!,
workflow: pluginWorkflow
}
workflowAsyncContext.run(pluginState, () => plugin.setup())
pluginStateMap.set(plugin, pluginState)
return config
},
run: () => {
const { sendEvent, stream: registerStream } = coreWorkflow.createContext()
sendEvent(pluginRegisterEvent.with())
return {
get plugins (): ReadonlySet<Plugin> {
return new Set(pluginSet)
},
async stop (): Promise<void> {
await registerStream.until(haltEvent).toArray()
const {
sendEvent,
stream: unregisterStream
} = coreWorkflow.createContext()
sendEvent(pluginUnregisterEvent.with())
await unregisterStream.until(haltEvent).toArray()
}
}
}
}
return config
}
66 changes: 66 additions & 0 deletions packages/core/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
InferWorkflowEventData,
Workflow,
workflowEvent,
WorkflowEventData
} from '@llama-flow/core'
import { AsyncLocalStorage } from 'node:async_hooks'

export type Plugin = {
setup (): void
}

/**
* @internal
*/
export type PluginState = {
description: string
workflow: Workflow
}

/**
* @internal
*/
function usePluginState () {
const workflow = workflowAsyncContext.getStore()
if (!workflow) {
throw new Error('No workflow is available in the current context')
}
return workflow
}

/**
* @internal
*/
export const workflowAsyncContext = new AsyncLocalStorage<PluginState>()

/**
* @internal
*/
export const pluginRegisterEvent = workflowEvent()

/**
* @internal
*/
export const pluginUnregisterEvent = workflowEvent()

export function useDescription (
description: string
) {
const pluginState = usePluginState()
pluginState.description = description
}

export function onRegister (
handler: (pluginRegister: WorkflowEventData<InferWorkflowEventData<typeof pluginRegisterEvent>>) => ReturnType<Workflow['handle']>
) {
const { workflow } = usePluginState()
workflow.handle([pluginRegisterEvent], handler)
}

export function onUnregister (
handler: (pluginUnregister: WorkflowEventData<InferWorkflowEventData<typeof pluginUnregisterEvent>>) => void
) {
const { workflow } = usePluginState()
workflow.handle([pluginUnregisterEvent], handler)
}
24 changes: 24 additions & 0 deletions packages/core/test/basic.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect, test, vi } from 'vitest'
import { createAthena } from '../src/index.js'
import {
onRegister,
onUnregister,
Plugin,
useDescription
} from '../src/plugin.js'

test('should able to setup athena', async () => {
const registerHandler = vi.fn()
const unregisterHandler = vi.fn()
const noopPlugin: Plugin = {
setup () {
useDescription('this plugin does nothing')
onRegister(registerHandler)
onUnregister(unregisterHandler)
}
}
const athena = createAthena().add(noopPlugin).run()
expect(registerHandler).toHaveBeenCalledOnce()
await athena.stop()
expect(unregisterHandler).toHaveBeenCalledOnce()
})
16 changes: 16 additions & 0 deletions packages/core/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"rootDir": ".",
"target": "esnext",
"module": "esnext",
"moduleResolution": "node16",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": [
"./src",
"./test"
]
}
Loading
Loading