100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
import {
|
|
JsonConsoleLogger,
|
|
LocalSecretService,
|
|
LocalVariablesService,
|
|
InMemoryQueueService,
|
|
} from '@pikku/core/services'
|
|
import { pikkuServices } from '../.pikku/pikku-types.gen.js'
|
|
import { TypedSecretService } from '../.pikku/secrets/pikku-secrets.gen.js'
|
|
import { TypedVariablesService } from '../.pikku/variables/pikku-variables.gen.js'
|
|
import { CFWorkerSchemaService } from '@pikku/schema-cfworker'
|
|
import { Kysely, CamelCasePlugin } from 'kysely'
|
|
import { LibsqlWebDialect } from '@pikku/kysely-sqlite'
|
|
import type { VercelAIAgentRunner } from '@pikku/ai-vercel'
|
|
import type { DB } from './types/db.types.js'
|
|
|
|
export const createSingletonServices = pikkuServices(async (config, existingServices) => {
|
|
const variables =
|
|
existingServices?.variables ?? new TypedVariablesService(new LocalVariablesService())
|
|
const secrets =
|
|
existingServices?.secrets ?? new TypedSecretService(new LocalSecretService(variables))
|
|
const logger = existingServices?.logger ?? new JsonConsoleLogger()
|
|
const schema = existingServices?.schema ?? new CFWorkerSchemaService(logger)
|
|
|
|
// In CF Workers and containers DATABASE_URL is injected by fabric.
|
|
// In local dev (pikku dev) existingServices.kysely is the node:sqlite instance
|
|
// set up by the CLI — prefer it over DATABASE_URL so sandbox mode always uses
|
|
// the local migrated SQLite rather than the remote Turso URL from the cascade.
|
|
// In tests existingServices.kysely is provided by the test support layer.
|
|
// node:sqlite / createNodeSqliteKysely is intentionally absent — it is not
|
|
// available in CF Workers. See tests/support/services.ts for the dev path.
|
|
let kysely: Kysely<DB> | undefined
|
|
if (existingServices?.kysely) {
|
|
kysely = existingServices.kysely as Kysely<DB>
|
|
} else {
|
|
const databaseUrl = await variables.get('DATABASE_URL')
|
|
if (databaseUrl) {
|
|
if (/^postgres(ql)?:\/\//.test(databaseUrl)) {
|
|
const [{ PostgresJSDialect }, postgres] = await Promise.all([
|
|
import('kysely-postgres-js'),
|
|
import('postgres'),
|
|
])
|
|
kysely = new Kysely<DB>({
|
|
dialect: new PostgresJSDialect({
|
|
postgres: postgres.default(databaseUrl),
|
|
}),
|
|
plugins: [new CamelCasePlugin()],
|
|
})
|
|
} else {
|
|
kysely = new Kysely<DB>({
|
|
dialect: new LibsqlWebDialect({ url: databaseUrl }),
|
|
plugins: [new CamelCasePlugin()],
|
|
})
|
|
}
|
|
} else {
|
|
throw new Error(
|
|
'kysely service not provided: set DATABASE_URL (Postgres or libsql) or pass kysely via existingServices',
|
|
)
|
|
}
|
|
}
|
|
|
|
const litellmProxyUrl = process.env.LITELLM_PROXY_URL ?? null
|
|
const litellmApiKey = process.env.LITELLM_API_KEY ?? null
|
|
let aiAgentRunner: VercelAIAgentRunner | undefined
|
|
if (litellmProxyUrl && litellmApiKey) {
|
|
// The AI SDKs (~3MB) are stubbed out of non-agent units at bundle time —
|
|
// only units with the `ai-model` capability keep them. So import them
|
|
// dynamically and guard on the module resolving to a real export; in a
|
|
// stubbed unit the import yields `{}` and the runner is simply not built.
|
|
const aiVercel = await import('@pikku/ai-vercel')
|
|
const aiSdk = await import('@ai-sdk/openai-compatible')
|
|
if (aiVercel.VercelAIAgentRunner && aiSdk.createOpenAICompatible) {
|
|
const litellmProvider = aiSdk.createOpenAICompatible({
|
|
name: 'litellm',
|
|
baseURL: litellmProxyUrl,
|
|
apiKey: litellmApiKey,
|
|
})
|
|
aiAgentRunner = new aiVercel.VercelAIAgentRunner({
|
|
openai: (modelId: string) => litellmProvider.chatModel(modelId),
|
|
anthropic: (modelId: string) => litellmProvider.chatModel(modelId),
|
|
google: (modelId: string) => litellmProvider.chatModel(modelId),
|
|
deepseek: (modelId: string) => litellmProvider.chatModel(modelId),
|
|
})
|
|
}
|
|
}
|
|
|
|
const queueService = existingServices?.queueService ?? new InMemoryQueueService()
|
|
|
|
return {
|
|
...(existingServices ?? {}),
|
|
config,
|
|
variables,
|
|
secrets,
|
|
logger,
|
|
schema,
|
|
kysely,
|
|
queueService,
|
|
...(aiAgentRunner ? { aiAgentRunner } : {}),
|
|
}
|
|
})
|