Browser-based OAuth flow using Anthropic's hosted callback. Users authorize on claude.ai, paste the code back, and tokens are encrypted and stored in D1. Includes auto-refresh, Bearer auth via custom fetch wrapper, and mcp_ tool name prefixing required by the OAuth endpoint. Also fixes provider-config save bug that required encryption key unconditionally — now only checks when API key is present.
111 lines
3.6 KiB
TypeScript
Executable File
111 lines
3.6 KiB
TypeScript
Executable File
import {
|
|
sqliteTable,
|
|
text,
|
|
integer,
|
|
} from "drizzle-orm/sqlite-core"
|
|
import { users, agentConversations } from "./schema"
|
|
|
|
// singleton config row (id = "global")
|
|
export const agentConfig = sqliteTable("agent_config", {
|
|
id: text("id").primaryKey(),
|
|
modelId: text("model_id").notNull(),
|
|
modelName: text("model_name").notNull(),
|
|
provider: text("provider").notNull(),
|
|
promptCost: text("prompt_cost").notNull(),
|
|
completionCost: text("completion_cost").notNull(),
|
|
contextLength: integer("context_length").notNull(),
|
|
maxCostPerMillion: text("max_cost_per_million"),
|
|
allowUserSelection: integer("allow_user_selection")
|
|
.notNull()
|
|
.default(1),
|
|
updatedBy: text("updated_by")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
updatedAt: text("updated_at").notNull(),
|
|
})
|
|
|
|
// per-user model preference
|
|
export const userModelPreference = sqliteTable(
|
|
"user_model_preference",
|
|
{
|
|
userId: text("user_id")
|
|
.primaryKey()
|
|
.references(() => users.id),
|
|
modelId: text("model_id").notNull(),
|
|
promptCost: text("prompt_cost").notNull(),
|
|
completionCost: text("completion_cost").notNull(),
|
|
updatedAt: text("updated_at").notNull(),
|
|
}
|
|
)
|
|
|
|
// per-user provider configuration
|
|
export const userProviderConfig = sqliteTable(
|
|
"user_provider_config",
|
|
{
|
|
userId: text("user_id")
|
|
.primaryKey()
|
|
.references(() => users.id),
|
|
providerType: text("provider_type").notNull(), // anthropic-oauth | anthropic-key | openrouter | ollama | custom
|
|
apiKey: text("api_key"), // encrypted, nullable
|
|
baseUrl: text("base_url"), // nullable
|
|
modelOverrides: text("model_overrides"), // JSON, nullable
|
|
isActive: integer("is_active").notNull().default(1),
|
|
updatedAt: text("updated_at").notNull(),
|
|
}
|
|
)
|
|
|
|
// one row per streamText invocation
|
|
export const agentUsage = sqliteTable("agent_usage", {
|
|
id: text("id").primaryKey(),
|
|
conversationId: text("conversation_id")
|
|
.notNull()
|
|
.references(() => agentConversations.id, {
|
|
onDelete: "cascade",
|
|
}),
|
|
userId: text("user_id")
|
|
.notNull()
|
|
.references(() => users.id),
|
|
modelId: text("model_id").notNull(),
|
|
promptTokens: integer("prompt_tokens")
|
|
.notNull()
|
|
.default(0),
|
|
completionTokens: integer("completion_tokens")
|
|
.notNull()
|
|
.default(0),
|
|
totalTokens: integer("total_tokens")
|
|
.notNull()
|
|
.default(0),
|
|
estimatedCost: text("estimated_cost").notNull(),
|
|
createdAt: text("created_at").notNull(),
|
|
})
|
|
|
|
// per-user Anthropic OAuth tokens (separate from provider config
|
|
// because OAuth needs refresh token + expiry tracking)
|
|
export const anthropicOauthTokens = sqliteTable(
|
|
"anthropic_oauth_tokens",
|
|
{
|
|
userId: text("user_id")
|
|
.primaryKey()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
accessToken: text("access_token").notNull(),
|
|
refreshToken: text("refresh_token").notNull(),
|
|
expiresAt: text("expires_at").notNull(),
|
|
updatedAt: text("updated_at").notNull(),
|
|
}
|
|
)
|
|
|
|
export type AgentConfig = typeof agentConfig.$inferSelect
|
|
export type NewAgentConfig = typeof agentConfig.$inferInsert
|
|
export type UserProviderConfig = typeof userProviderConfig.$inferSelect
|
|
export type NewUserProviderConfig = typeof userProviderConfig.$inferInsert
|
|
export type AgentUsage = typeof agentUsage.$inferSelect
|
|
export type NewAgentUsage = typeof agentUsage.$inferInsert
|
|
export type UserModelPreference =
|
|
typeof userModelPreference.$inferSelect
|
|
export type NewUserModelPreference =
|
|
typeof userModelPreference.$inferInsert
|
|
export type AnthropicOauthToken =
|
|
typeof anthropicOauthTokens.$inferSelect
|
|
export type NewAnthropicOauthToken =
|
|
typeof anthropicOauthTokens.$inferInsert
|