Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
55 changes: 55 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -63,3 +64,57 @@ jobs:
body_path: notes.md
prerelease: ${{ steps.version.outputs.prerelease == 'true' }}
generate_release_notes: false

publish-opentypebb:
runs-on: ubuntu-latest
needs: release
if: needs.release.result == 'success'
permissions:
contents: read
packages: write

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://npm.pkg.github.com
cache: pnpm

- name: Check if version already published
id: check
working-directory: packages/opentypebb
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if npm view "@traderalice/opentypebb@$VERSION" version --registry=https://npm.pkg.github.com 2>/dev/null; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Build
if: steps.check.outputs.exists == 'false'
working-directory: packages/opentypebb
run: |
pnpm install --frozen-lockfile
pnpm build

- name: Publish to GitHub Packages
if: steps.check.outputs.exists == 'false'
working-directory: packages/opentypebb
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish to npmjs
if: steps.check.outputs.exists == 'false'
working-directory: packages/opentypebb
run: npm publish --registry=https://registry.npmjs.org
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
File renamed without changes.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-alice",
"version": "0.9.0-beta.1",
"version": "0.9.0-beta.2",
"description": "File-based trading agent engine",
"type": "module",
"scripts": {
Expand Down Expand Up @@ -47,7 +47,7 @@
"grammy": "^1.40.0",
"hono": "^4.12.5",
"json5": "^2.2.3",
"opentypebb": "link:./packages/opentypebb",
"@traderalice/opentypebb": "link:./packages/opentypebb",
"pino": "^10.3.1",
"playwright-core": "1.58.2",
"sharp": "^0.34.5",
Expand Down
23 changes: 22 additions & 1 deletion packages/opentypebb/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "opentypebb",
"name": "@traderalice/opentypebb",
"version": "0.1.0",
"description": "TypeScript port of OpenBB Platform — financial data infrastructure",
"type": "module",
Expand All @@ -13,6 +13,27 @@
"types": "./src/server.ts"
}
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/TraderAlice/OpenAlice.git",
"directory": "packages/opentypebb"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./server": {
"import": "./dist/server.js",
"types": "./dist/server.d.ts"
}
}
},
"scripts": {
"dev": "tsx src/server.ts",
"build": "tsup",
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/ai-providers/agent-sdk/agent-sdk-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@

import { resolve } from 'node:path'
import type { Tool } from 'ai'
import type { ProviderResult, ProviderEvent } from '../../core/ai-provider.js'
import type { GenerateProvider, GenerateInput, GenerateOpts } from '../../core/ai-provider.js'
import type { ProviderResult, ProviderEvent, GenerateProvider, GenerateInput, GenerateOpts } from '../types.js'
import type { AgentSdkConfig, AgentSdkOverride } from './query.js'
import { readAgentConfig } from '../../core/config.js'
import { extractMediaFromToolResultContent } from '../../core/media.js'
Expand Down
2 changes: 1 addition & 1 deletion src/ai-providers/agent-sdk/tool-bridge.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Tool bridge — converts ToolCenter's Vercel AI SDK tools to an Agent SDK MCP server.
*
* Reuses the same pattern as `src/plugins/mcp.ts` (extract .shape, wrap execute),
* Reuses the same pattern as `src/server/mcp.ts` (extract .shape, wrap execute),
* but targets `createSdkMcpServer()` instead of `@modelcontextprotocol/sdk McpServer`.
*/

Expand Down
3 changes: 1 addition & 2 deletions src/ai-providers/claude-code/claude-code-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
*/

import { resolve } from 'node:path'
import type { ProviderResult, ProviderEvent } from '../../core/ai-provider.js'
import type { GenerateProvider, GenerateInput, GenerateOpts } from '../../core/ai-provider.js'
import type { ProviderResult, ProviderEvent, GenerateProvider, GenerateInput, GenerateOpts } from '../types.js'
import type { ClaudeCodeConfig } from './types.js'
import { readAgentConfig } from '../../core/config.js'
import { extractMediaFromToolResultContent } from '../../core/media.js'
Expand Down
64 changes: 64 additions & 0 deletions src/ai-providers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { SessionStore, SDKModelMessage } from '../core/session.js'
import type { CompactionConfig, CompactionResult } from '../core/compaction.js'
import type { MediaAttachment } from '../core/types.js'

// ==================== Provider Events ====================

/** Streaming event emitted by AI providers during generation. */
export type ProviderEvent =
| { type: 'tool_use'; id: string; name: string; input: unknown }
| { type: 'tool_result'; tool_use_id: string; content: string }
| { type: 'text'; text: string }
| { type: 'done'; result: ProviderResult }

// ==================== Types ====================

export interface ProviderResult {
text: string
media: MediaAttachment[]
mediaUrls?: string[]
}

// ==================== GenerateProvider ====================

/**
* Input prepared by AgentCenter, dispatched by provider.inputKind.
*
* - 'text': Claude Code / Agent SDK — single string prompt with <chat_history> baked in.
* - 'messages': Vercel AI SDK — structured ModelMessage[] (history carried natively).
*/
export type GenerateInput =
| { kind: 'text'; prompt: string; systemPrompt?: string }
| { kind: 'messages'; messages: SDKModelMessage[]; systemPrompt?: string }

/** Per-request options passed through to the underlying provider. */
export interface GenerateOpts {
disabledTools?: string[]
vercelAiSdk?: { provider: string; model: string; baseUrl?: string; apiKey?: string }
agentSdk?: { model?: string; apiKey?: string; baseUrl?: string }
}

/**
* Slim provider interface — pure data-source adapter.
*
* Does NOT touch session management. AgentCenter prepares the input,
* the provider calls the backend and yields ProviderEvents.
*/
export interface GenerateProvider {
/** Which input format this provider expects. */
readonly inputKind: 'text' | 'messages'
/** Session log provenance tag. */
readonly providerTag: 'vercel-ai' | 'claude-code' | 'agent-sdk'
/** Stateless one-shot prompt (used for compaction summarization, etc.). */
ask(prompt: string): Promise<ProviderResult>
/** Stream events from the backend. Yields tool_use/tool_result/text, then done. */
generate(input: GenerateInput, opts?: GenerateOpts): AsyncIterable<ProviderEvent>
/**
* Optional: custom compaction strategy. If implemented, AgentCenter delegates
* compaction to the provider instead of using the default compactIfNeeded.
*
* Use case: providers with native server-side compaction (e.g. Anthropic API
* compact-2026-01-12) can bypass the local JSONL-based summarization.
*/
compact?(session: SessionStore, config: CompactionConfig): Promise<CompactionResult>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import type { LanguageModel } from 'ai'
import { readAIProviderConfig } from './config.js'
import { readAIProviderConfig } from '../../core/config.js'

/** Result includes the model plus a cache key for change detection. */
export interface ModelFromConfig {
Expand Down
5 changes: 2 additions & 3 deletions src/ai-providers/vercel-ai-sdk/vercel-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
*/

import type { ModelMessage, Tool } from 'ai'
import type { ProviderResult, ProviderEvent } from '../../core/ai-provider.js'
import type { GenerateProvider, GenerateInput, GenerateOpts } from '../../core/ai-provider.js'
import type { ProviderResult, ProviderEvent, GenerateProvider, GenerateInput, GenerateOpts } from '../types.js'
import type { Agent } from './agent.js'
import type { MediaAttachment } from '../../core/types.js'
import { extractMediaFromToolOutput } from '../../core/media.js'
import { createModelFromConfig, type ModelOverride } from '../../core/model-factory.js'
import { createModelFromConfig, type ModelOverride } from './model-factory.js'
import { createAgent } from './agent.js'
import { createChannel } from '../../core/async-channel.js'

Expand Down
13 changes: 7 additions & 6 deletions src/connectors/telegram/telegram-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { askClaudeCode } from '../../ai-providers/claude-code/index.js'
import type { ClaudeCodeConfig } from '../../ai-providers/claude-code/index.js'
import { SessionStore } from '../../core/session'
import { forceCompact } from '../../core/compaction'
import { readAIConfig, writeAIConfig, type AIBackend } from '../../core/ai-config'
import type { ConnectorCenter, Connector } from '../../core/connector-center.js'
import { readAIBackend, writeAIBackend, type AIBackend } from '../../core/config'
import type { ConnectorCenter } from '../../core/connector-center.js'
import type { Connector } from '../types.js'

const MAX_MESSAGE_LENGTH = 4096

Expand Down Expand Up @@ -82,7 +83,7 @@ export class TelegramPlugin implements Plugin {

// ── Commands ──
bot.command('status', async (ctx) => {
const aiConfig = await readAIConfig()
const aiConfig = await readAIBackend()
await this.sendReply(ctx.chat.id, `Engine is running. Provider: ${BACKEND_LABELS[aiConfig.backend]}`)
})

Expand All @@ -106,7 +107,7 @@ export class TelegramPlugin implements Plugin {
try {
if (data.startsWith('provider:')) {
const backend = data.slice('provider:'.length) as AIBackend
await writeAIConfig(backend)
await writeAIBackend(backend)
await ctx.answerCallbackQuery({ text: `Switched to ${BACKEND_LABELS[backend]}` })

// Edit the original settings message in-place
Expand Down Expand Up @@ -170,7 +171,7 @@ export class TelegramPlugin implements Plugin {

// ── Initialize and get bot info ──
await bot.init()
const aiConfig = await readAIConfig()
const aiConfig = await readAIBackend()
console.log(`telegram plugin: connected as @${bot.botInfo.username} (backend: ${aiConfig.backend})`)

// ── Register connector for outbound delivery (heartbeat / cron responses) ──
Expand Down Expand Up @@ -320,7 +321,7 @@ export class TelegramPlugin implements Plugin {
}

private async sendSettingsMenu(chatId: number) {
const aiConfig = await readAIConfig()
const aiConfig = await readAIBackend()
const ccLabel = aiConfig.backend === 'claude-code' ? '> Claude Code' : 'Claude Code'
const aiLabel = aiConfig.backend === 'vercel-ai-sdk' ? '> Vercel AI SDK' : 'Vercel AI SDK'
const sdkLabel = aiConfig.backend === 'agent-sdk' ? '> Agent SDK' : 'Agent SDK'
Expand Down
58 changes: 58 additions & 0 deletions src/connectors/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { MediaAttachment } from '../core/types.js'
import type { StreamableResult } from '../core/ai-provider.js'

// ==================== Send Types ====================

/** Structured payload for outbound send (heartbeat, cron, manual, etc.). */
export interface SendPayload {
/** Whether this is a chat message or a notification. */
kind: 'message' | 'notification'
/** The text content to send. */
text: string
/** Media attachments (e.g. screenshots from tools). */
media?: MediaAttachment[]
/** Where this payload originated from. */
source?: 'heartbeat' | 'cron' | 'manual'
}

/** Result of a send() call. */
export interface SendResult {
/** Whether the message was actually sent (false for pull-based connectors). */
delivered: boolean
}

// ==================== Connector Interface ====================

/** Discoverable capabilities a connector may support. */
export interface ConnectorCapabilities {
/** Can push messages proactively (heartbeat/cron). False for pull-based. */
push: boolean
/** Can send media attachments (images). */
media: boolean
}

/**
* A connector that can send outbound messages to a user.
*
* Each plugin (Telegram, Web, MCP-ask) implements this interface and
* registers itself with the ConnectorCenter.
*/
export interface Connector {
/** Channel identifier, e.g. "telegram", "web", "mcp-ask". */
readonly channel: string
/** Recipient identifier (chat id, "default", session id, etc.). */
readonly to: string
/** What this connector can do. */
readonly capabilities: ConnectorCapabilities
/** Send a structured payload through this connector. */
send(payload: SendPayload): Promise<SendResult>
/**
* Optional: stream AI response events to the client in real-time.
* Connectors that support this can push ProviderEvents (tool_use, tool_result, text)
* as they arrive, then deliver the final result at the end.
*
* If not implemented, ConnectorCenter falls back to draining the stream
* and calling send() with the completed result.
*/
sendStream?(stream: StreamableResult, meta?: Pick<SendPayload, 'kind' | 'source'>): Promise<SendResult>
}
5 changes: 2 additions & 3 deletions src/connectors/web/routes/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Hono } from 'hono'
import { loadConfig, writeConfigSection, readAIProviderConfig, readOpenbbConfig, validSections, type ConfigSection } from '../../../core/config.js'
import { readAIConfig, writeAIConfig, type AIBackend } from '../../../core/ai-config.js'
import { loadConfig, writeConfigSection, readAIProviderConfig, readOpenbbConfig, validSections, writeAIBackend, type ConfigSection, type AIBackend } from '../../../core/config.js'

interface ConfigRouteOpts {
onConnectorsChange?: () => Promise<void>
Expand All @@ -26,7 +25,7 @@ export function createConfigRoutes(opts?: ConfigRouteOpts) {
if (backend !== 'claude-code' && backend !== 'vercel-ai-sdk' && backend !== 'agent-sdk') {
return c.json({ error: 'Invalid backend. Must be "claude-code", "vercel-ai-sdk", or "agent-sdk".' }, 400)
}
await writeAIConfig(backend as AIBackend)
await writeAIBackend(backend as AIBackend)
return c.json({ backend })
} catch (err) {
return c.json({ error: String(err) }, 500)
Expand Down
2 changes: 1 addition & 1 deletion src/connectors/web/web-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { serveStatic } from '@hono/node-server/serve-static'
import { resolve } from 'node:path'
import type { Plugin, EngineContext } from '../../core/types.js'
import { SessionStore, type ContentBlock } from '../../core/session.js'
import type { Connector, SendPayload } from '../../core/connector-center.js'
import type { Connector, SendPayload } from '../types.js'
import type { StreamableResult } from '../../core/ai-provider.js'
import { persistMedia } from '../../core/media-store.js'
import { readWebSubchannels } from '../../core/config.js'
Expand Down
Loading
Loading