mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-27 20:29:44 +02:00
use rowboat model gateway when logged in
This commit is contained in:
parent
9a18ba3537
commit
b4c022258b
8 changed files with 121 additions and 6 deletions
|
|
@ -21,6 +21,8 @@ import { ServiceEvent } from '@x/shared/dist/service-events.js';
|
||||||
import container from '@x/core/dist/di/container.js';
|
import container from '@x/core/dist/di/container.js';
|
||||||
import { listOnboardingModels } from '@x/core/dist/models/models-dev.js';
|
import { listOnboardingModels } from '@x/core/dist/models/models-dev.js';
|
||||||
import { testModelConnection } from '@x/core/dist/models/models.js';
|
import { testModelConnection } from '@x/core/dist/models/models.js';
|
||||||
|
import { isSignedIn } from '@x/core/dist/account/account.js';
|
||||||
|
import { listGatewayModels } from '@x/core/dist/models/gateway.js';
|
||||||
import type { IModelConfigRepo } from '@x/core/dist/models/repo.js';
|
import type { IModelConfigRepo } from '@x/core/dist/models/repo.js';
|
||||||
import type { IOAuthRepo } from '@x/core/dist/auth/repo.js';
|
import type { IOAuthRepo } from '@x/core/dist/auth/repo.js';
|
||||||
import { IGranolaConfigRepo } from '@x/core/dist/knowledge/granola/repo.js';
|
import { IGranolaConfigRepo } from '@x/core/dist/knowledge/granola/repo.js';
|
||||||
|
|
@ -369,6 +371,9 @@ export function setupIpcHandlers() {
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
'models:list': async () => {
|
'models:list': async () => {
|
||||||
|
if (await isSignedIn()) {
|
||||||
|
return await listGatewayModels();
|
||||||
|
}
|
||||||
return await listOnboardingModels();
|
return await listOnboardingModels();
|
||||||
},
|
},
|
||||||
'models:test': async (_event, args) => {
|
'models:test': async (_event, args) => {
|
||||||
|
|
|
||||||
8
apps/x/packages/core/src/account/account.ts
Normal file
8
apps/x/packages/core/src/account/account.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import container from '../di/container.js';
|
||||||
|
import { IOAuthRepo } from '../auth/repo.js';
|
||||||
|
|
||||||
|
export async function isSignedIn(): Promise<boolean> {
|
||||||
|
const oauthRepo = container.resolve<IOAuthRepo>('oauthRepo');
|
||||||
|
const { tokens } = await oauthRepo.read('rowboat');
|
||||||
|
return !!tokens;
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,8 @@ import { isBlocked, extractCommandNames } from "../application/lib/command-execu
|
||||||
import container from "../di/container.js";
|
import container from "../di/container.js";
|
||||||
import { IModelConfigRepo } from "../models/repo.js";
|
import { IModelConfigRepo } from "../models/repo.js";
|
||||||
import { createProvider } from "../models/models.js";
|
import { createProvider } from "../models/models.js";
|
||||||
|
import { isSignedIn } from "../account/account.js";
|
||||||
|
import { getGatewayProvider } from "../models/gateway.js";
|
||||||
import { IAgentsRepo } from "./repo.js";
|
import { IAgentsRepo } from "./repo.js";
|
||||||
import { IMonotonicallyIncreasingIdGenerator } from "../application/lib/id-gen.js";
|
import { IMonotonicallyIncreasingIdGenerator } from "../application/lib/id-gen.js";
|
||||||
import { IBus } from "../application/lib/bus.js";
|
import { IBus } from "../application/lib/bus.js";
|
||||||
|
|
@ -705,7 +707,9 @@ export async function* streamAgent({
|
||||||
const tools = await buildTools(agent);
|
const tools = await buildTools(agent);
|
||||||
|
|
||||||
// set up provider + model
|
// set up provider + model
|
||||||
const provider = createProvider(modelConfig.provider);
|
const provider = await isSignedIn()
|
||||||
|
? await getGatewayProvider()
|
||||||
|
: createProvider(modelConfig.provider);
|
||||||
const knowledgeGraphAgents = ["note_creation", "email-draft", "meeting-prep"];
|
const knowledgeGraphAgents = ["note_creation", "email-draft", "meeting-prep"];
|
||||||
const modelId = (knowledgeGraphAgents.includes(state.agentName!) && modelConfig.knowledgeGraphModel)
|
const modelId = (knowledgeGraphAgents.includes(state.agentName!) && modelConfig.knowledgeGraphModel)
|
||||||
? modelConfig.knowledgeGraphModel
|
? modelConfig.knowledgeGraphModel
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import type { ToolContext } from "./exec-tool.js";
|
||||||
import { generateText } from "ai";
|
import { generateText } from "ai";
|
||||||
import { createProvider } from "../../models/models.js";
|
import { createProvider } from "../../models/models.js";
|
||||||
import { IModelConfigRepo } from "../../models/repo.js";
|
import { IModelConfigRepo } from "../../models/repo.js";
|
||||||
|
import { isSignedIn } from "../../account/account.js";
|
||||||
|
import { getGatewayProvider } from "../../models/gateway.js";
|
||||||
// Parser libraries are loaded dynamically inside parseFile.execute()
|
// Parser libraries are loaded dynamically inside parseFile.execute()
|
||||||
// to avoid pulling pdfjs-dist's DOM polyfills into the main bundle.
|
// to avoid pulling pdfjs-dist's DOM polyfills into the main bundle.
|
||||||
// Import paths are computed so esbuild cannot statically resolve them.
|
// Import paths are computed so esbuild cannot statically resolve them.
|
||||||
|
|
@ -861,7 +863,9 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
|
||||||
// Resolve model config from DI container
|
// Resolve model config from DI container
|
||||||
const modelConfigRepo = container.resolve<IModelConfigRepo>('modelConfigRepo');
|
const modelConfigRepo = container.resolve<IModelConfigRepo>('modelConfigRepo');
|
||||||
const modelConfig = await modelConfigRepo.getConfig();
|
const modelConfig = await modelConfigRepo.getConfig();
|
||||||
const provider = createProvider(modelConfig.provider);
|
const provider = await isSignedIn()
|
||||||
|
? await getGatewayProvider()
|
||||||
|
: createProvider(modelConfig.provider);
|
||||||
const model = provider.languageModel(modelConfig.model);
|
const model = provider.languageModel(modelConfig.model);
|
||||||
|
|
||||||
const userPrompt = prompt || 'Convert this file to well-structured markdown.';
|
const userPrompt = prompt || 'Convert this file to well-structured markdown.';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { SUPABASE_PROJECT_URL } from '../config/env.js';
|
||||||
const SUPABASE_PROJECT_URL = 'http://127.0.0.1:54321';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discovery configuration - how to get OAuth endpoints
|
* Discovery configuration - how to get OAuth endpoints
|
||||||
|
|
@ -56,7 +55,7 @@ const providerConfigs: ProviderConfig = {
|
||||||
rowboat: {
|
rowboat: {
|
||||||
discovery: {
|
discovery: {
|
||||||
mode: 'issuer',
|
mode: 'issuer',
|
||||||
issuer: `${SUPABASE_PROJECT_URL}/.well-known/oauth-authorization-server`,
|
issuer: `${SUPABASE_PROJECT_URL}/auth/v1/.well-known/oauth-authorization-server`,
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
mode: 'dcr',
|
mode: 'dcr',
|
||||||
|
|
|
||||||
5
apps/x/packages/core/src/config/env.ts
Normal file
5
apps/x/packages/core/src/config/env.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const ROWBOAT_AI_GATEWAY_BASE_URL =
|
||||||
|
process.env.ROWBOAT_AI_GATEWAY_BASE_URL || 'http://localhost:3002/v1';
|
||||||
|
|
||||||
|
export const SUPABASE_PROJECT_URL =
|
||||||
|
process.env.SUPABASE_PROJECT_URL || 'http://127.0.0.1:54321';
|
||||||
86
apps/x/packages/core/src/models/gateway.ts
Normal file
86
apps/x/packages/core/src/models/gateway.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import { ProviderV2 } from '@ai-sdk/provider';
|
||||||
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||||
|
import container from '../di/container.js';
|
||||||
|
import { IOAuthRepo } from '../auth/repo.js';
|
||||||
|
import { IClientRegistrationRepo } from '../auth/client-repo.js';
|
||||||
|
import { getProviderConfig } from '../auth/providers.js';
|
||||||
|
import * as oauthClient from '../auth/oauth-client.js';
|
||||||
|
import { ROWBOAT_AI_GATEWAY_BASE_URL } from '../config/env.js';
|
||||||
|
|
||||||
|
async function getAccessToken(): Promise<string> {
|
||||||
|
const oauthRepo = container.resolve<IOAuthRepo>('oauthRepo');
|
||||||
|
const { tokens } = await oauthRepo.read('rowboat');
|
||||||
|
if (!tokens) {
|
||||||
|
throw new Error('Not signed into Rowboat');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oauthClient.isTokenExpired(tokens)) {
|
||||||
|
return tokens.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokens.refresh_token) {
|
||||||
|
throw new Error('Rowboat token expired and no refresh token available. Please sign in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerConfig = getProviderConfig('rowboat');
|
||||||
|
if (providerConfig.discovery.mode !== 'issuer') {
|
||||||
|
throw new Error('Rowboat provider requires issuer discovery mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientRepo = container.resolve<IClientRegistrationRepo>('clientRegistrationRepo');
|
||||||
|
const registration = await clientRepo.getClientRegistration('rowboat');
|
||||||
|
if (!registration) {
|
||||||
|
throw new Error('Rowboat client not registered. Please sign in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await oauthClient.discoverConfiguration(
|
||||||
|
providerConfig.discovery.issuer,
|
||||||
|
registration.client_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshed = await oauthClient.refreshTokens(
|
||||||
|
config,
|
||||||
|
tokens.refresh_token,
|
||||||
|
tokens.scopes,
|
||||||
|
);
|
||||||
|
await oauthRepo.upsert('rowboat', { tokens: refreshed });
|
||||||
|
|
||||||
|
return refreshed.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGatewayProvider(): Promise<ProviderV2> {
|
||||||
|
const accessToken = await getAccessToken();
|
||||||
|
return createOpenRouter({
|
||||||
|
baseURL: ROWBOAT_AI_GATEWAY_BASE_URL,
|
||||||
|
apiKey: accessToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderSummary = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
models: Array<{
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
release_date?: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function listGatewayModels(): Promise<{ providers: ProviderSummary[] }> {
|
||||||
|
const accessToken = await getAccessToken();
|
||||||
|
const response = await fetch(`${ROWBOAT_AI_GATEWAY_BASE_URL}/models`, {
|
||||||
|
headers: { Authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Gateway /v1/models failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
const body = await response.json() as { data: Array<{ id: string }> };
|
||||||
|
const models = body.data.map((m) => ({ id: m.id }));
|
||||||
|
return {
|
||||||
|
providers: [{
|
||||||
|
id: 'rowboat',
|
||||||
|
name: 'Rowboat',
|
||||||
|
models,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,8 @@ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
||||||
import { LlmModelConfig, LlmProvider } from "@x/shared/dist/models.js";
|
import { LlmModelConfig, LlmProvider } from "@x/shared/dist/models.js";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
import { isSignedIn } from "../account/account.js";
|
||||||
|
import { getGatewayProvider } from "./gateway.js";
|
||||||
|
|
||||||
export const Provider = LlmProvider;
|
export const Provider = LlmProvider;
|
||||||
export const ModelConfig = LlmModelConfig;
|
export const ModelConfig = LlmModelConfig;
|
||||||
|
|
@ -78,7 +80,9 @@ export async function testModelConnection(
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), effectiveTimeout);
|
const timeout = setTimeout(() => controller.abort(), effectiveTimeout);
|
||||||
try {
|
try {
|
||||||
const provider = createProvider(providerConfig);
|
const provider = await isSignedIn()
|
||||||
|
? await getGatewayProvider()
|
||||||
|
: createProvider(providerConfig);
|
||||||
const languageModel = provider.languageModel(model);
|
const languageModel = provider.languageModel(model);
|
||||||
await generateText({
|
await generateText({
|
||||||
model: languageModel,
|
model: languageModel,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue