mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 05:00:16 +00:00
refactor(ai): register api providers
This commit is contained in:
parent
3256d3c083
commit
c725135a76
24 changed files with 897 additions and 629 deletions
|
|
@ -1,7 +1,10 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, stream } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi } from "../src/types.js";
|
||||
import type { Api, Context, Model, StreamOptions } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
|
@ -12,7 +15,7 @@ const [geminiCliToken, openaiCodexToken] = await Promise.all([
|
|||
resolveApiKey("openai-codex"),
|
||||
]);
|
||||
|
||||
async function testAbortSignal<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testAbortSignal<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
const context: Context = {
|
||||
messages: [
|
||||
{
|
||||
|
|
@ -56,7 +59,7 @@ async function testAbortSignal<TApi extends Api>(llm: Model<TApi>, options: Opti
|
|||
expect(followUp.content.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
async function testImmediateAbort<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testImmediateAbort<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
const controller = new AbortController();
|
||||
|
||||
controller.abort();
|
||||
|
|
@ -69,7 +72,7 @@ async function testImmediateAbort<TApi extends Api>(llm: Model<TApi>, options: O
|
|||
expect(response.stopReason).toBe("aborted");
|
||||
}
|
||||
|
||||
async function testAbortThenNewMessage<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testAbortThenNewMessage<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
// First request: abort immediately before any response content arrives
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, AssistantMessage, Context, Model, OptionsForApi, UserMessage } from "../src/types.js";
|
||||
import type { Api, AssistantMessage, Context, Model, StreamOptions, UserMessage } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
|
@ -16,7 +19,7 @@ const oauthTokens = await Promise.all([
|
|||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
async function testEmptyMessage<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testEmptyMessage<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
// Test with completely empty content array
|
||||
const emptyMessage: UserMessage = {
|
||||
role: "user",
|
||||
|
|
@ -41,7 +44,7 @@ async function testEmptyMessage<TApi extends Api>(llm: Model<TApi>, options: Opt
|
|||
}
|
||||
}
|
||||
|
||||
async function testEmptyStringMessage<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testEmptyStringMessage<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
// Test with empty string content
|
||||
const context: Context = {
|
||||
messages: [
|
||||
|
|
@ -66,7 +69,7 @@ async function testEmptyStringMessage<TApi extends Api>(llm: Model<TApi>, option
|
|||
}
|
||||
}
|
||||
|
||||
async function testWhitespaceOnlyMessage<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testWhitespaceOnlyMessage<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
// Test with whitespace-only content
|
||||
const context: Context = {
|
||||
messages: [
|
||||
|
|
@ -91,7 +94,7 @@ async function testWhitespaceOnlyMessage<TApi extends Api>(llm: Model<TApi>, opt
|
|||
}
|
||||
}
|
||||
|
||||
async function testEmptyAssistantMessage<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testEmptyAssistantMessage<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
// Test with empty assistant message in conversation flow
|
||||
// User -> Empty Assistant -> User
|
||||
const emptyAssistant: AssistantMessage = {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import { Type } from "@sinclair/typebox";
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { Api, Context, Model, Tool, ToolResultMessage } from "../src/index.js";
|
||||
import { complete, getModel } from "../src/index.js";
|
||||
import type { OptionsForApi } from "../src/types.js";
|
||||
import type { StreamOptions } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
|
@ -26,7 +29,7 @@ const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken
|
|||
* 2. Providers correctly pass images from tool results to the LLM
|
||||
* 3. The LLM can see and describe images returned by tools
|
||||
*/
|
||||
async function handleToolWithImageResult<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function handleToolWithImageResult<TApi extends Api>(model: Model<TApi>, options?: StreamOptionsWithExtras) {
|
||||
// Check if the model supports images
|
||||
if (!model.input.includes("image")) {
|
||||
console.log(`Skipping tool image result test - model ${model.id} doesn't support images`);
|
||||
|
|
@ -114,7 +117,10 @@ async function handleToolWithImageResult<TApi extends Api>(model: Model<TApi>, o
|
|||
* 2. Providers correctly pass both text and images from tool results to the LLM
|
||||
* 3. The LLM can see both the text and images in tool results
|
||||
*/
|
||||
async function handleToolWithTextAndImageResult<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function handleToolWithTextAndImageResult<TApi extends Api>(
|
||||
model: Model<TApi>,
|
||||
options?: StreamOptionsWithExtras,
|
||||
) {
|
||||
// Check if the model supports images
|
||||
if (!model.input.includes("image")) {
|
||||
console.log(`Skipping tool text+image result test - model ${model.id} doesn't support images`);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import { fileURLToPath } from "url";
|
|||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete, stream } from "../src/stream.js";
|
||||
import type { Api, Context, ImageContent, Model, OptionsForApi, Tool, ToolResultMessage } from "../src/types.js";
|
||||
import type { Api, Context, ImageContent, Model, StreamOptions, Tool, ToolResultMessage } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { StringEnum } from "../src/utils/typebox-helpers.js";
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
|
|
@ -42,7 +45,7 @@ const calculatorTool: Tool<typeof calculatorSchema> = {
|
|||
parameters: calculatorSchema,
|
||||
};
|
||||
|
||||
async function basicTextGeneration<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function basicTextGeneration<TApi extends Api>(model: Model<TApi>, options?: StreamOptionsWithExtras) {
|
||||
const context: Context = {
|
||||
systemPrompt: "You are a helpful assistant. Be concise.",
|
||||
messages: [{ role: "user", content: "Reply with exactly: 'Hello test successful'", timestamp: Date.now() }],
|
||||
|
|
@ -71,7 +74,7 @@ async function basicTextGeneration<TApi extends Api>(model: Model<TApi>, options
|
|||
);
|
||||
}
|
||||
|
||||
async function handleToolCall<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function handleToolCall<TApi extends Api>(model: Model<TApi>, options?: StreamOptionsWithExtras) {
|
||||
const context: Context = {
|
||||
systemPrompt: "You are a helpful assistant that uses tools when asked.",
|
||||
messages: [
|
||||
|
|
@ -149,7 +152,7 @@ async function handleToolCall<TApi extends Api>(model: Model<TApi>, options?: Op
|
|||
}
|
||||
}
|
||||
|
||||
async function handleStreaming<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function handleStreaming<TApi extends Api>(model: Model<TApi>, options?: StreamOptionsWithExtras) {
|
||||
let textStarted = false;
|
||||
let textChunks = "";
|
||||
let textCompleted = false;
|
||||
|
|
@ -179,7 +182,7 @@ async function handleStreaming<TApi extends Api>(model: Model<TApi>, options?: O
|
|||
expect(response.content.some((b) => b.type === "text")).toBeTruthy();
|
||||
}
|
||||
|
||||
async function handleThinking<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function handleThinking<TApi extends Api>(model: Model<TApi>, options?: StreamOptionsWithExtras) {
|
||||
let thinkingStarted = false;
|
||||
let thinkingChunks = "";
|
||||
let thinkingCompleted = false;
|
||||
|
|
@ -216,7 +219,7 @@ async function handleThinking<TApi extends Api>(model: Model<TApi>, options?: Op
|
|||
expect(response.content.some((b) => b.type === "thinking")).toBeTruthy();
|
||||
}
|
||||
|
||||
async function handleImage<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function handleImage<TApi extends Api>(model: Model<TApi>, options?: StreamOptionsWithExtras) {
|
||||
// Check if the model supports images
|
||||
if (!model.input.includes("image")) {
|
||||
console.log(`Skipping image test - model ${model.id} doesn't support images`);
|
||||
|
|
@ -263,7 +266,7 @@ async function handleImage<TApi extends Api>(model: Model<TApi>, options?: Optio
|
|||
}
|
||||
}
|
||||
|
||||
async function multiTurn<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
|
||||
async function multiTurn<TApi extends Api>(model: Model<TApi>, options?: StreamOptionsWithExtras) {
|
||||
const context: Context = {
|
||||
systemPrompt: "You are a helpful assistant that can use tools to answer questions.",
|
||||
messages: [
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { stream } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi } from "../src/types.js";
|
||||
import type { Api, Context, Model, StreamOptions } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
|
@ -16,7 +19,7 @@ const oauthTokens = await Promise.all([
|
|||
]);
|
||||
const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken, openaiCodexToken] = oauthTokens;
|
||||
|
||||
async function testTokensOnAbort<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testTokensOnAbort<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
const context: Context = {
|
||||
messages: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import { Type } from "@sinclair/typebox";
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi, Tool } from "../src/types.js";
|
||||
import type { Api, Context, Model, StreamOptions, Tool } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
|
@ -28,10 +31,7 @@ const calculateTool: Tool = {
|
|||
parameters: calculateSchema,
|
||||
};
|
||||
|
||||
async function testToolCallWithoutResult<TApi extends Api>(
|
||||
model: Model<TApi>,
|
||||
options: OptionsForApi<TApi> = {} as OptionsForApi<TApi>,
|
||||
) {
|
||||
async function testToolCallWithoutResult<TApi extends Api>(model: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
// Step 1: Create context with the calculate tool
|
||||
const context: Context = {
|
||||
systemPrompt: "You are a helpful assistant. Use the calculate tool when asked to perform calculations.",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi, Usage } from "../src/types.js";
|
||||
import type { Api, Context, Model, StreamOptions, Usage } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
|
@ -45,7 +48,7 @@ Remember: Always be helpful and concise.`;
|
|||
|
||||
async function testTotalTokensWithCache<TApi extends Api>(
|
||||
llm: Model<TApi>,
|
||||
options: OptionsForApi<TApi> = {} as OptionsForApi<TApi>,
|
||||
options: StreamOptionsWithExtras = {},
|
||||
): Promise<{ first: Usage; second: Usage }> {
|
||||
// First request - no cache
|
||||
const context1: Context = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import { Type } from "@sinclair/typebox";
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getModel } from "../src/models.js";
|
||||
import { complete } from "../src/stream.js";
|
||||
import type { Api, Context, Model, OptionsForApi, ToolResultMessage } from "../src/types.js";
|
||||
import type { Api, Context, Model, StreamOptions, ToolResultMessage } from "../src/types.js";
|
||||
|
||||
type StreamOptionsWithExtras = StreamOptions & Record<string, unknown>;
|
||||
|
||||
import { hasAzureOpenAICredentials, resolveAzureDeploymentName } from "./azure-utils.js";
|
||||
import { hasBedrockCredentials } from "./bedrock-utils.js";
|
||||
import { resolveApiKey } from "./oauth.js";
|
||||
|
|
@ -31,7 +34,7 @@ const [anthropicOAuthToken, githubCopilotToken, geminiCliToken, antigravityToken
|
|||
* "The request body is not valid JSON: no low surrogate in string: line 1 column 197667"
|
||||
*/
|
||||
|
||||
async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
const toolCallId = llm.provider === "mistral" ? "testtool1" : "test_1";
|
||||
// Simulate a tool that returns emoji
|
||||
const context: Context = {
|
||||
|
|
@ -118,7 +121,7 @@ async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>, option
|
|||
expect(response.content.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
async function testRealWorldLinkedInData<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testRealWorldLinkedInData<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
const toolCallId = llm.provider === "mistral" ? "linkedin1" : "linkedin_1";
|
||||
const context: Context = {
|
||||
systemPrompt: "You are a helpful assistant.",
|
||||
|
|
@ -207,7 +210,7 @@ Unanswered Comments: 2
|
|||
expect(response.content.some((b) => b.type === "text")).toBe(true);
|
||||
}
|
||||
|
||||
async function testUnpairedHighSurrogate<TApi extends Api>(llm: Model<TApi>, options: OptionsForApi<TApi> = {}) {
|
||||
async function testUnpairedHighSurrogate<TApi extends Api>(llm: Model<TApi>, options: StreamOptionsWithExtras = {}) {
|
||||
const toolCallId = llm.provider === "mistral" ? "testtool2" : "test_2";
|
||||
const context: Context = {
|
||||
systemPrompt: "You are a helpful assistant.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue