Phase 1-2: Fix type conflicts and implement prompt submission
- Resolve duplicate DroneStatus enum (import from @gadget/api) - Fix IAiProvider interface conflict with DB→runtime mapper - Add callId to ToolCallMessage and ChatToolCallSchema - Fix ChatTurnStats schema field name (thinkingTokenCount) - Add provider/selectedModel to ChatSession interface and model - Implement CodeSession.onSubmitPrompt() to create ChatTurn and send work orders - Add drone/chat session tracking to CodeSession - Add unit tests for CodeSession (9 tests, all passing)
This commit is contained in:
parent
6591da3496
commit
8fe75b8c1c
@ -5,41 +5,41 @@
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Fix Type Errors & Interface Conflicts
|
||||
## Phase 1: Fix Type Errors & Interface Conflicts ✅ COMPLETE
|
||||
|
||||
### 1.1 Resolve Duplicate `DroneStatus` Enum
|
||||
- **File:** `gadget-drone/src/services/platform.ts`
|
||||
- **Action:** Remove local `DroneStatus` enum, import from `@gadget/api`
|
||||
- **Status:** ⬜ Pending
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
### 1.2 Resolve `IAiProvider` Interface Conflict
|
||||
- **Files:**
|
||||
- `packages/api/src/interfaces/ai-provider.ts` (Mongoose document)
|
||||
- `packages/ai/src/api.ts` (runtime config)
|
||||
- **Action:** Create mapper in `gadget-drone/src/services/ai.ts` to convert DB model → runtime config
|
||||
- **Status:** ⬜ Pending
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
### 1.3 Fix `ToolCallMessage` Signature
|
||||
- **File:** `packages/api/src/messages/drone.ts:26-30`
|
||||
- **Issue:** Missing `callId` parameter required by `IChatToolCall`
|
||||
- **Action:** Add `callId: string` as first parameter
|
||||
- **Status:** ⬜ Pending
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
### 1.4 Fix `ChatTurnStats` Schema Mismatch
|
||||
- **File:** `gadget-code/src/models/chat-turn.ts:70-76`
|
||||
- **Issue:** Schema uses `thinkingTokens`, interface uses `thinkingTokenCount`
|
||||
- **Action:** Standardize on `thinkingTokenCount` in schema
|
||||
- **Status:** ⬜ Pending
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
### 1.5 Fix `ChatToolCallSchema` Missing `callId`
|
||||
- **File:** `gadget-code/src/models/chat-turn.ts:31-36`
|
||||
- **Issue:** Schema doesn't include required `callId` field
|
||||
- **Action:** Add `callId: { type: String, required: true }` to schema
|
||||
- **Status:** ⬜ Pending
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Implement Prompt Submission Flow
|
||||
## Phase 2: Implement Prompt Submission Flow ✅ COMPLETE
|
||||
|
||||
### 2.1 Implement `CodeSession.onSubmitPrompt()`
|
||||
- **File:** `gadget-code/src/lib/code-session.ts:58-60`
|
||||
@ -50,12 +50,23 @@
|
||||
- Emit `processWorkOrder` to drone
|
||||
- Update `ChatTurn` with drone acknowledgment
|
||||
- **Missing:** Track `selectedDrone`, `chatSession`, `project` in `CodeSession`
|
||||
- **Status:** ⬜ Pending
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
### 2.2 Add Drone Selection to `CodeSession`
|
||||
- **File:** `gadget-code/src/lib/code-session.ts`
|
||||
- **Action:** Add properties and methods to track selected drone, chat session, project
|
||||
- **Status:** ⬜ Pending
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
### 2.3 Add `provider` and `selectedModel` to ChatSession
|
||||
- **Files:**
|
||||
- `packages/api/src/interfaces/chat-session.ts`
|
||||
- `gadget-code/src/models/chat-session.ts`
|
||||
- **Status:** ✅ Complete
|
||||
|
||||
### 2.4 Unit Tests for CodeSession
|
||||
- **File:** `gadget-code/tests/code-session.test.ts`
|
||||
- **Tests:** 9 tests covering prompt submission flow
|
||||
- **Status:** ✅ Complete (all passing)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -2,20 +2,30 @@
|
||||
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
|
||||
// All Rights Reserved
|
||||
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
GadgetSocket,
|
||||
SocketSession,
|
||||
SocketSessionType,
|
||||
} from "./socket-session";
|
||||
import { IChatSession, IDroneRegistration, IProject, IUser } from "@gadget/api";
|
||||
import {
|
||||
IChatSession,
|
||||
IDroneRegistration,
|
||||
IProject,
|
||||
IUser,
|
||||
ChatTurnStatus,
|
||||
} from "@gadget/api";
|
||||
|
||||
import SocketService from "../services/socket.ts";
|
||||
import { ChatTurn } from "../models/chat-turn.ts";
|
||||
|
||||
export class CodeSession extends SocketSession {
|
||||
protected type: SocketSessionType = SocketSessionType.Code;
|
||||
|
||||
protected project: IProject | undefined;
|
||||
protected chatSession: IChatSession | undefined;
|
||||
protected selectedDrone: IDroneRegistration | undefined;
|
||||
protected currentTurnId: Types.ObjectId | undefined;
|
||||
|
||||
constructor(socket: GadgetSocket, user: IUser) {
|
||||
super(socket, user);
|
||||
@ -28,6 +38,24 @@ export class CodeSession extends SocketSession {
|
||||
this.socket.on("submitPrompt", this.onSubmitPrompt.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected drone for this code session.
|
||||
*/
|
||||
setSelectedDrone(registration: IDroneRegistration): void {
|
||||
this.selectedDrone = registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active chat session and project for this code session.
|
||||
*/
|
||||
setChatSession(
|
||||
chatSession: IChatSession,
|
||||
project: IProject,
|
||||
): void {
|
||||
this.chatSession = chatSession;
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the IDE sends a requestSessionLock event to lock a gadget-drone
|
||||
* instance to this code session.
|
||||
@ -50,12 +78,90 @@ export class CodeSession extends SocketSession {
|
||||
project,
|
||||
chatSession,
|
||||
(success: boolean, chatSessionId: string): void => {
|
||||
if (success) {
|
||||
this.selectedDrone = registration;
|
||||
this.chatSession = chatSession;
|
||||
this.project = project;
|
||||
}
|
||||
cb(success, chatSessionId);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the IDE submits a prompt to be processed by the agent.
|
||||
* Creates a ChatTurn document and sends a work order to the selected drone.
|
||||
*/
|
||||
async onSubmitPrompt(content: string): Promise<void> {
|
||||
this.log.debug("prompt received", { content });
|
||||
if (!this.selectedDrone) {
|
||||
this.log.warn("prompt rejected: no drone selected");
|
||||
throw new Error("No drone selected");
|
||||
}
|
||||
if (!this.chatSession) {
|
||||
this.log.warn("prompt rejected: no chat session active");
|
||||
throw new Error("No chat session active");
|
||||
}
|
||||
if (!this.project) {
|
||||
this.log.warn("prompt rejected: no project selected");
|
||||
throw new Error("No project selected");
|
||||
}
|
||||
|
||||
const droneSession = SocketService.getDroneSession(this.selectedDrone);
|
||||
|
||||
const turn = new ChatTurn({
|
||||
createdAt: new Date(),
|
||||
user: this.user._id,
|
||||
project: this.project._id,
|
||||
session: this.chatSession._id,
|
||||
provider: this.chatSession.provider,
|
||||
llm: this.chatSession.selectedModel,
|
||||
mode: this.chatSession.mode,
|
||||
status: ChatTurnStatus.Processing,
|
||||
prompts: {
|
||||
user: content,
|
||||
system: undefined,
|
||||
},
|
||||
toolCalls: [],
|
||||
subagents: [],
|
||||
stats: {
|
||||
toolCallCount: 0,
|
||||
inputTokens: 0,
|
||||
thinkingTokenCount: 0,
|
||||
responseTokens: 0,
|
||||
durationMs: 0,
|
||||
durationLabel: "pending",
|
||||
},
|
||||
});
|
||||
await turn.save();
|
||||
this.currentTurnId = turn._id;
|
||||
|
||||
this.log.info("ChatTurn created", {
|
||||
turnId: turn._id.toHexString(),
|
||||
chatSessionId: this.chatSession._id.toHexString(),
|
||||
});
|
||||
|
||||
droneSession.socket.emit(
|
||||
"processWorkOrder",
|
||||
this.selectedDrone,
|
||||
this.project,
|
||||
this.chatSession,
|
||||
turn,
|
||||
(success: boolean, message?: string) => {
|
||||
if (success) {
|
||||
this.log.info("work order accepted by drone", {
|
||||
turnId: turn._id.toHexString(),
|
||||
message,
|
||||
});
|
||||
} else {
|
||||
this.log.error("work order rejected by drone", {
|
||||
turnId: turn._id.toHexString(),
|
||||
message,
|
||||
});
|
||||
turn.status = ChatTurnStatus.Error;
|
||||
turn.response = message || "Drone rejected work order";
|
||||
turn.save();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,8 @@ export const ChatSessionSchema = new Schema<IChatSession>({
|
||||
default: ChatSessionMode.Build,
|
||||
required: true,
|
||||
},
|
||||
provider: { type: Schema.Types.ObjectId, required: true, ref: "AiProvider" },
|
||||
selectedModel: { type: String, required: true },
|
||||
stats: {
|
||||
turnCount: { type: Number, default: 0, required: true },
|
||||
toolCallCount: { type: Number, default: 0, required: true },
|
||||
|
||||
@ -29,6 +29,7 @@ export const ChatTurnStatsSchema = new Schema<IChatTurnStats>({
|
||||
});
|
||||
|
||||
export const ChatToolCallSchema = new Schema<IChatToolCall>({
|
||||
callId: { type: String, required: true },
|
||||
name: { type: String, required: true },
|
||||
parameters: { type: String, required: false },
|
||||
response: { type: String, required: false },
|
||||
@ -69,7 +70,7 @@ export const ChatTurnSchema = new Schema<IChatTurn>({
|
||||
stats: {
|
||||
toolCallCount: { type: Number, default: 0, required: true },
|
||||
inputTokens: { type: Number, default: 0, required: true },
|
||||
thinkingTokens: { type: Number, default: 0, required: true },
|
||||
thinkingTokenCount: { type: Number, default: 0, required: true },
|
||||
responseTokens: { type: Number, default: 0, required: true },
|
||||
durationMs: { type: Number, default: 0, required: true },
|
||||
durationLabel: { type: String, default: "pending", required: true },
|
||||
|
||||
209
gadget-code/tests/code-session.test.ts
Normal file
209
gadget-code/tests/code-session.test.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { Types } from 'mongoose';
|
||||
import { CodeSession } from '../src/lib/code-session';
|
||||
import { IChatSession, IProject, IUser, IDroneRegistration, ChatTurnStatus } from '@gadget/api';
|
||||
import SocketService from '../src/services/socket';
|
||||
import { ChatTurn } from '../src/models/chat-turn';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../src/services/socket');
|
||||
vi.mock('../src/models/chat-turn');
|
||||
|
||||
describe('CodeSession', () => {
|
||||
let mockSocket: any;
|
||||
let mockUser: IUser;
|
||||
let mockDrone: IDroneRegistration;
|
||||
let mockChatSession: IChatSession;
|
||||
let mockProject: IProject;
|
||||
let codeSession: CodeSession;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockSocket = {
|
||||
id: 'test-socket-id',
|
||||
on: vi.fn(),
|
||||
emit: vi.fn(),
|
||||
};
|
||||
|
||||
mockUser = {
|
||||
_id: new Types.ObjectId(),
|
||||
email: 'test@example.com',
|
||||
displayName: 'Test User',
|
||||
} as IUser;
|
||||
|
||||
mockDrone = {
|
||||
_id: new Types.ObjectId(),
|
||||
hostname: 'test-host',
|
||||
workspaceDir: '/test/workspace',
|
||||
status: 'available',
|
||||
} as IDroneRegistration;
|
||||
|
||||
mockChatSession = {
|
||||
_id: new Types.ObjectId(),
|
||||
name: 'Test Session',
|
||||
mode: 'build',
|
||||
provider: new Types.ObjectId(),
|
||||
selectedModel: 'llama3.1',
|
||||
} as IChatSession;
|
||||
|
||||
mockProject = {
|
||||
_id: new Types.ObjectId(),
|
||||
slug: 'test-project',
|
||||
name: 'Test Project',
|
||||
} as IProject;
|
||||
|
||||
codeSession = new CodeSession(mockSocket, mockUser);
|
||||
});
|
||||
|
||||
describe('setSelectedDrone', () => {
|
||||
it('should set the selected drone', () => {
|
||||
codeSession.setSelectedDrone(mockDrone);
|
||||
// Can't directly access private property, but we can verify through behavior
|
||||
expect(() => codeSession.setSelectedDrone(mockDrone)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setChatSession', () => {
|
||||
it('should set the chat session and project', () => {
|
||||
codeSession.setChatSession(mockChatSession, mockProject);
|
||||
expect(() => codeSession.setChatSession(mockChatSession, mockProject)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSubmitPrompt', () => {
|
||||
it('should throw error if no drone is selected', async () => {
|
||||
codeSession.setChatSession(mockChatSession, mockProject);
|
||||
|
||||
await expect(codeSession.onSubmitPrompt('test prompt'))
|
||||
.rejects.toThrow('No drone selected');
|
||||
});
|
||||
|
||||
it('should throw error if no chat session is active', async () => {
|
||||
codeSession.setSelectedDrone(mockDrone);
|
||||
|
||||
await expect(codeSession.onSubmitPrompt('test prompt'))
|
||||
.rejects.toThrow('No chat session active');
|
||||
});
|
||||
|
||||
it('should throw error if no project is selected', async () => {
|
||||
codeSession.setSelectedDrone(mockDrone);
|
||||
codeSession.setChatSession(mockChatSession, undefined as any);
|
||||
|
||||
await expect(codeSession.onSubmitPrompt('test prompt'))
|
||||
.rejects.toThrow('No project selected');
|
||||
});
|
||||
|
||||
it('should create a ChatTurn and emit processWorkOrder to drone', async () => {
|
||||
codeSession.setSelectedDrone(mockDrone);
|
||||
codeSession.setChatSession(mockChatSession, mockProject);
|
||||
|
||||
const mockTurn = {
|
||||
_id: new Types.ObjectId(),
|
||||
save: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
vi.mocked(ChatTurn).mockImplementation(function() {
|
||||
return mockTurn as any;
|
||||
});
|
||||
|
||||
const mockDroneSession = {
|
||||
socket: {
|
||||
emit: vi.fn(),
|
||||
},
|
||||
};
|
||||
vi.mocked(SocketService.getDroneSession).mockReturnValue(mockDroneSession as any);
|
||||
|
||||
await codeSession.onSubmitPrompt('test prompt');
|
||||
|
||||
expect(ChatTurn).toHaveBeenCalledWith(expect.objectContaining({
|
||||
user: mockUser._id,
|
||||
project: mockProject._id,
|
||||
session: mockChatSession._id,
|
||||
provider: mockChatSession.provider,
|
||||
llm: mockChatSession.selectedModel,
|
||||
status: ChatTurnStatus.Processing,
|
||||
prompts: {
|
||||
user: 'test prompt',
|
||||
system: undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
expect(mockTurn.save).toHaveBeenCalled();
|
||||
expect(mockDroneSession.socket.emit).toHaveBeenCalledWith(
|
||||
'processWorkOrder',
|
||||
mockDrone,
|
||||
mockProject,
|
||||
mockChatSession,
|
||||
mockTurn,
|
||||
expect.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it('should update ChatTurn to Error status if drone rejects work order', async () => {
|
||||
codeSession.setSelectedDrone(mockDrone);
|
||||
codeSession.setChatSession(mockChatSession, mockProject);
|
||||
|
||||
const mockTurn = {
|
||||
_id: new Types.ObjectId(),
|
||||
status: ChatTurnStatus.Processing,
|
||||
response: '',
|
||||
save: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
vi.mocked(ChatTurn).mockImplementation(function() {
|
||||
return mockTurn as any;
|
||||
});
|
||||
|
||||
const mockDroneSession = {
|
||||
socket: {
|
||||
emit: vi.fn((event, ...args) => {
|
||||
const callback = args[args.length - 1];
|
||||
callback(false, 'Drone is busy');
|
||||
}),
|
||||
},
|
||||
};
|
||||
vi.mocked(SocketService.getDroneSession).mockReturnValue(mockDroneSession as any);
|
||||
|
||||
await codeSession.onSubmitPrompt('test prompt');
|
||||
|
||||
expect(mockTurn.status).toBe(ChatTurnStatus.Error);
|
||||
expect(mockTurn.response).toBe('Drone is busy');
|
||||
expect(mockTurn.save).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('onRequestSessionLock', () => {
|
||||
it('should set selected drone, chat session, and project on success', () => {
|
||||
const mockDroneSession = {
|
||||
socket: {
|
||||
emit: vi.fn((event, ...args) => {
|
||||
const callback = args[args.length - 1];
|
||||
callback(true, mockChatSession._id.toHexString());
|
||||
}),
|
||||
},
|
||||
};
|
||||
vi.mocked(SocketService.getDroneSession).mockReturnValue(mockDroneSession as any);
|
||||
|
||||
const callback = vi.fn();
|
||||
codeSession.onRequestSessionLock(mockDrone, mockProject, mockChatSession, callback);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith(true, mockChatSession._id.toHexString());
|
||||
});
|
||||
|
||||
it('should not set session data on failure', () => {
|
||||
const mockDroneSession = {
|
||||
socket: {
|
||||
emit: vi.fn((event, ...args) => {
|
||||
const callback = args[args.length - 1];
|
||||
callback(false, '');
|
||||
}),
|
||||
},
|
||||
};
|
||||
vi.mocked(SocketService.getDroneSession).mockReturnValue(mockDroneSession as any);
|
||||
|
||||
const callback = vi.fn();
|
||||
codeSession.onRequestSessionLock(mockDrone, mockProject, mockChatSession, callback);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith(false, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -10,10 +10,8 @@ import { input as inqInput, password as inqPassword } from "@inquirer/prompts";
|
||||
|
||||
import AgentService, { IAgentWorkOrder } from "./services/agent.ts";
|
||||
import AiService from "./services/ai.ts";
|
||||
import PlatformService, {
|
||||
DroneStatus,
|
||||
PlatformRegistration,
|
||||
} from "./services/platform.ts";
|
||||
import PlatformService, { PlatformRegistration } from "./services/platform.ts";
|
||||
import { DroneStatus } from "@gadget/api";
|
||||
|
||||
import { GadgetProcess } from "./lib/process.ts";
|
||||
import {
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IAiChatOptions,
|
||||
type IAiProvider,
|
||||
type IContextChatMessage,
|
||||
} from "@gadget/ai";
|
||||
import { IChatSession, IChatTurn, IProject, IUser } from "@gadget/api";
|
||||
@ -50,17 +50,6 @@ class AgentService extends GadgetService {
|
||||
return "[all tool calls are stubbed out]";
|
||||
}
|
||||
|
||||
const modelConfig = {
|
||||
provider: turn.provider,
|
||||
modelId: turn.llm,
|
||||
params: {
|
||||
reasoning: false,
|
||||
temperature: 0.8,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
},
|
||||
};
|
||||
|
||||
const context = this.buildSessionContext(workOrder);
|
||||
const chatOptions: IAiChatOptions = {
|
||||
systemPrompt: turn.prompts.system,
|
||||
@ -72,7 +61,15 @@ class AgentService extends GadgetService {
|
||||
do {
|
||||
const response = await AiService.chat(
|
||||
turn.provider,
|
||||
modelConfig,
|
||||
{
|
||||
modelId: turn.llm,
|
||||
params: {
|
||||
reasoning: false,
|
||||
temperature: 0.8,
|
||||
topP: 0.9,
|
||||
topK: 40,
|
||||
},
|
||||
},
|
||||
chatOptions,
|
||||
);
|
||||
keepProcessing = (response.toolCalls?.length ?? 0) > 0;
|
||||
@ -99,7 +96,13 @@ class AgentService extends GadgetService {
|
||||
}
|
||||
|
||||
buildSessionContext(workOrder: IAgentWorkOrder): IContextChatMessage[] {
|
||||
const user: IUser = workOrder.turn.session.user as IUser;
|
||||
const session = workOrder.turn.session;
|
||||
if (session instanceof Types.ObjectId || !session.user) {
|
||||
throw new Error(
|
||||
"ChatSession must be populated with user data",
|
||||
);
|
||||
}
|
||||
const user: IUser = session.user as IUser;
|
||||
const messages: IContextChatMessage[] = [];
|
||||
|
||||
for (const turn of workOrder.context) {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import { Types } from "mongoose";
|
||||
import { GadgetService } from "../lib/service.ts";
|
||||
import {
|
||||
type IAiChatOptions,
|
||||
@ -9,10 +10,25 @@ import {
|
||||
type IAiGenerateOptions,
|
||||
type IAiGenerateResponse,
|
||||
type IAiModelConfig,
|
||||
type IAiProvider,
|
||||
type IAiProvider as AiProviderConfig,
|
||||
type IAiResponseStreamFn,
|
||||
createAiApi,
|
||||
} from "@gadget/ai";
|
||||
import { IAiProvider as DbAiProvider } from "@gadget/api";
|
||||
|
||||
/**
|
||||
* Drone-specific model config that accepts the database provider type.
|
||||
*/
|
||||
export interface IDroneModelConfig {
|
||||
provider: DbAiProvider | Types.ObjectId;
|
||||
modelId: string;
|
||||
params: {
|
||||
reasoning: boolean;
|
||||
temperature: number;
|
||||
topP: number;
|
||||
topK: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstraction of the backend AI APIs (Ollama, OpenAI) that provides one
|
||||
@ -40,54 +56,81 @@ class AiService extends GadgetService {
|
||||
this.log.info("stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a database IAiProvider document to a runtime IAiProvider config.
|
||||
* The DB model uses `apiType` and extends Mongoose Document, while the runtime
|
||||
* config uses `sdk` and is a plain object.
|
||||
*/
|
||||
mapDbProviderToConfig(
|
||||
provider: DbAiProvider | Types.ObjectId,
|
||||
): AiProviderConfig {
|
||||
if (provider instanceof Types.ObjectId) {
|
||||
throw new Error(
|
||||
"Provider must be populated, not an ObjectId reference",
|
||||
);
|
||||
}
|
||||
return {
|
||||
_id: provider._id.toHexString(),
|
||||
name: provider.name,
|
||||
sdk: provider.apiType, // map apiType → sdk
|
||||
baseUrl: provider.baseUrl,
|
||||
apiKey: provider.apiKey,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the list of models available from the provider, then queries the
|
||||
* models for their individual capabilities. The results are cached in the Gadget
|
||||
*/
|
||||
async discovery(provider: IAiProvider): Promise<void> {
|
||||
async discovery(provider: DbAiProvider | Types.ObjectId): Promise<void> {
|
||||
const config = this.mapDbProviderToConfig(provider);
|
||||
this.log.info("discovering provider model list", {
|
||||
name: provider.name,
|
||||
sdk: provider.sdk,
|
||||
name: config.name,
|
||||
sdk: config.sdk,
|
||||
});
|
||||
const api = this.getApi(provider);
|
||||
const api = this.getApi(config);
|
||||
const response = await api.listModels();
|
||||
this.log.debug("listModels response", { response });
|
||||
}
|
||||
|
||||
async generate(
|
||||
provider: IAiProvider,
|
||||
model: IAiModelConfig,
|
||||
provider: DbAiProvider | Types.ObjectId,
|
||||
model: Omit<IAiModelConfig, "provider">,
|
||||
options: IAiGenerateOptions,
|
||||
streamCallback?: IAiResponseStreamFn,
|
||||
): Promise<IAiGenerateResponse> {
|
||||
const config = this.mapDbProviderToConfig(provider);
|
||||
this.log.info("calling provider to generate a response", {
|
||||
name: provider.name,
|
||||
sdk: provider.sdk,
|
||||
name: config.name,
|
||||
sdk: config.sdk,
|
||||
haveStreamCallback: !!streamCallback,
|
||||
options,
|
||||
});
|
||||
const api = this.getApi(provider);
|
||||
return api.generate(model, options, streamCallback);
|
||||
const api = this.getApi(config);
|
||||
const modelConfig: IAiModelConfig = { ...model, provider: config };
|
||||
return api.generate(modelConfig, options, streamCallback);
|
||||
}
|
||||
|
||||
async chat(
|
||||
provider: IAiProvider,
|
||||
model: IAiModelConfig,
|
||||
provider: DbAiProvider | Types.ObjectId,
|
||||
model: Omit<IAiModelConfig, "provider">,
|
||||
options: IAiChatOptions,
|
||||
streamCallback?: IAiResponseStreamFn,
|
||||
): Promise<IAiChatResponse> {
|
||||
const config = this.mapDbProviderToConfig(provider);
|
||||
this.log.info("calling provider to process chat", {
|
||||
provider: provider.name,
|
||||
sdk: provider.sdk,
|
||||
provider: config.name,
|
||||
sdk: config.sdk,
|
||||
haveStreamCallback: !!streamCallback,
|
||||
model,
|
||||
options,
|
||||
});
|
||||
const api = this.getApi(provider);
|
||||
return await api.chat(model, options, streamCallback);
|
||||
const api = this.getApi(config);
|
||||
const modelConfig: IAiModelConfig = { ...model, provider: config };
|
||||
return await api.chat(modelConfig, options, streamCallback);
|
||||
}
|
||||
|
||||
getApi(provider: IAiProvider) {
|
||||
getApi(provider: AiProviderConfig) {
|
||||
return createAiApi(provider, this.log);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,13 +9,7 @@ import path from "node:path";
|
||||
import os from "node:os";
|
||||
|
||||
import { GadgetService } from "../lib/service.ts";
|
||||
|
||||
export enum DroneStatus {
|
||||
Starting = "starting",
|
||||
Available = "available",
|
||||
Busy = "busy",
|
||||
Offline = "offline",
|
||||
}
|
||||
import { DroneStatus } from "@gadget/api";
|
||||
|
||||
export interface PlatformRegistration {
|
||||
_id: string; // your drone's registration ID, channel, and queue
|
||||
|
||||
@ -26,6 +26,8 @@ export interface IChatSession extends Document {
|
||||
project: IProject | Types.ObjectId;
|
||||
name: string;
|
||||
mode: ChatSessionMode;
|
||||
provider: Types.ObjectId;
|
||||
selectedModel: string;
|
||||
stats: {
|
||||
turnCount: number;
|
||||
toolCallCount: number;
|
||||
|
||||
@ -24,6 +24,7 @@ export type ThinkingMessage = (content: string) => void;
|
||||
export type ResponseMessage = (content: string) => void;
|
||||
|
||||
export type ToolCallMessage = (
|
||||
callId: string,
|
||||
name: string,
|
||||
params: string,
|
||||
response: string,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user