Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions openrouter/server/tools/embeddings/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,32 @@ export const createGenerateEmbeddingsTool = (env: Env) =>
const { input, model, encoding_format, dimensions } = context;
const sdk = new OpenRouter({ apiKey: getOpenRouterApiKey(env) });

console.log({input, model, encoding_format, dimensions});
const inputCount = Array.isArray(input) ? input.length : 1;
const inputLength = Array.isArray(input)
? input.reduce((sum: number, s: string) => sum + s.length, 0)
: input.length;
console.log({
inputCount,
inputLength,
model,
encoding_format,
dimensions,
});
const result = await sdk.embeddings.generate({
input: input,
model: model,
model: model,
encodingFormat: encoding_format,
dimensions: dimensions,
});

console.log({result});

const resultAny = result as any;
const embeddingsCount = Array.isArray(resultAny.data)
? resultAny.data.length
: 0;
console.log({ embeddingsCount, model: resultAny.model });

return {
data: result
data: result,
};
},
});
22 changes: 13 additions & 9 deletions openrouter/server/tools/embeddings/list-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import { createPrivateTool } from "@decocms/runtime/tools";
import { getOpenRouterApiKey } from "server/lib/env.ts";
import { z } from "zod";
import { OpenRouterClient } from "../../lib/openrouter-client.ts";
import type { Env } from "../../main.ts";
import { createCollectionBindings } from "@decocms/bindings/collections";
import { createCollectionBindings } from "@decocms/bindings/collections";
import { OpenRouter } from "@openrouter/sdk";

const EMBEDDING_MODELS_BINDING = createCollectionBindings("EMBEDDING_MODELS", z.object({
const EMBEDDING_MODELS_BINDING = createCollectionBindings(
"EMBEDDING_MODELS",
z.object({
id: z.string(),
name: z.string(),
description: z.string().optional(),
Expand All @@ -20,14 +21,18 @@ import { OpenRouter } from "@openrouter/sdk";
prompt: z.string(),
completion: z.string(),
}),
}), { readOnly: true });
}),
{ readOnly: true },
);

const LIST_BINDING = EMBEDDING_MODELS_BINDING.find(
(b) => b.name === "COLLECTION_EMBEDDING_MODELS_LIST",
);

if (!LIST_BINDING?.inputSchema || !LIST_BINDING?.outputSchema) {
throw new Error("COLLECTION_EMBEDDING_MODELS_LIST binding not found or missing schemas");
throw new Error(
"COLLECTION_EMBEDDING_MODELS_LIST binding not found or missing schemas",
);
}

export const createListEmbeddingModelsTool = (env: Env) =>
Expand All @@ -39,11 +44,10 @@ export const createListEmbeddingModelsTool = (env: Env) =>
"Use this to discover which embedding models are available before generating embeddings." +
"Prefer to use a search",
inputSchema: LIST_BINDING.inputSchema,
outputSchema: z.object({
items: z.unknown(),
}),
outputSchema: z.object({
items: z.unknown(),
}),
execute: async () => {

const sdk = new OpenRouter({ apiKey: getOpenRouterApiKey(env) });
const models = (await sdk.embeddings.listModels()).data;

Expand Down
161 changes: 77 additions & 84 deletions openrouter/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,105 +2,98 @@ import { BaseCollectionEntity } from "@decocms/bindings/collections";

export const OPENROUTER_PROVIDER = "openrouter" as const;
type ExtendedCollectionEntity = BaseCollectionEntity & {
[key: string]: unknown;
[key: string]: unknown;
};

export function applyWhereFilter(
items: ExtendedCollectionEntity[],
where: Record<string, unknown>,
items: ExtendedCollectionEntity[],
where: Record<string, unknown>,
): ExtendedCollectionEntity[] {
const whereAny = where as {
operator?: string;
conditions?: Record<string, unknown>[];
field?: string[];
value?: unknown;
};
const whereAny = where as {
operator?: string;
conditions?: Record<string, unknown>[];
field?: string[];
value?: unknown;
};

if (whereAny.operator === "and" && whereAny.conditions) {
let filtered = items;
for (const condition of whereAny.conditions) {
filtered = applyWhereFilter(filtered, condition);
}
return filtered;
if (whereAny.operator === "and" && whereAny.conditions) {
let filtered = items;
for (const condition of whereAny.conditions) {
filtered = applyWhereFilter(filtered, condition);
}
return filtered;
}

if (whereAny.operator === "or" && whereAny.conditions) {
const results = new Set<ExtendedCollectionEntity>();
for (const condition of whereAny.conditions) {
applyWhereFilter(items, condition).forEach((m) => {
results.add(m);
});
}
return Array.from(results);
if (whereAny.operator === "or" && whereAny.conditions) {
const results = new Set<ExtendedCollectionEntity>();
for (const condition of whereAny.conditions) {
applyWhereFilter(items, condition).forEach((m) => {
results.add(m);
});
}
return Array.from(results);
}

if (whereAny.field && whereAny.operator && whereAny.value !== undefined) {
const field = whereAny.field[0];
return items.filter((item: ExtendedCollectionEntity) => {
if (field === "id" || field === "title") {
const modelValue = field === "id" ? item.id : item.name;
if (whereAny.operator === "eq") {
return modelValue === whereAny.value;
}
if (
whereAny.operator === "like" ||
whereAny.operator === "contains"
) {
return String(modelValue)
.toLowerCase()
.includes(String(whereAny.value).toLowerCase());
}
if (
whereAny.operator === "in" && Array.isArray(whereAny.value)
) {
return whereAny.value.includes(modelValue);
}
}
if (field === "provider") {
// All models are from OpenRouter
if (whereAny.operator === "eq") {
return OPENROUTER_PROVIDER === whereAny.value;
}
if (
whereAny.operator === "in" && Array.isArray(whereAny.value)
) {
return whereAny.value.includes(OPENROUTER_PROVIDER);
}
}
return true;
});
}
if (whereAny.field && whereAny.operator && whereAny.value !== undefined) {
const field = whereAny.field[0];
return items.filter((item: ExtendedCollectionEntity) => {
if (field === "id" || field === "title") {
const modelValue = field === "id" ? item.id : item.name;
if (whereAny.operator === "eq") {
return modelValue === whereAny.value;
}
if (whereAny.operator === "like" || whereAny.operator === "contains") {
return String(modelValue)
.toLowerCase()
.includes(String(whereAny.value).toLowerCase());
}
if (whereAny.operator === "in" && Array.isArray(whereAny.value)) {
return whereAny.value.includes(modelValue);
}
}
if (field === "provider") {
// All models are from OpenRouter
if (whereAny.operator === "eq") {
return OPENROUTER_PROVIDER === whereAny.value;
}
if (whereAny.operator === "in" && Array.isArray(whereAny.value)) {
return whereAny.value.includes(OPENROUTER_PROVIDER);
}
}
return true;
});
}

return items;
return items;
}

export function applyOrderBy(
models: ExtendedCollectionEntity[],
orderBy: Array<{ field: string[]; direction?: string }>,
models: ExtendedCollectionEntity[],
orderBy: Array<{ field: string[]; direction?: string }>,
): ExtendedCollectionEntity[] {
const sorted = [...models];
for (const order of orderBy.reverse()) {
const field = order.field[0];
const direction = order.direction === "desc" ? -1 : 1;
const sorted = [...models];
for (const order of orderBy.reverse()) {
const field = order.field[0];
const direction = order.direction === "desc" ? -1 : 1;

sorted.sort((a, b) => {
let aVal: string;
let bVal: string;
sorted.sort((a, b) => {
let aVal: string;
let bVal: string;

if (field === "id") {
aVal = a.id;
bVal = b.id;
} else if (field === "title") {
aVal = a.name as string;
bVal = b.name as string;
} else {
return 0;
}
if (field === "id") {
aVal = a.id;
bVal = b.id;
} else if (field === "title") {
aVal = a.name as string;
bVal = b.name as string;
} else {
return 0;
}

if (aVal < bVal) return -1 * direction;
if (aVal > bVal) return 1 * direction;
return 0;
});
}
return sorted;
if (aVal < bVal) return -1 * direction;
if (aVal > bVal) return 1 * direction;
return 0;
});
}
return sorted;
}
37 changes: 37 additions & 0 deletions shared/mesh-chat/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,37 @@ export async function callDecopilotAPI(
toolApprovalLevel: "yolo" as const,
};

console.log(`[MeshChat] ========== callDecopilotAPI ==========`);
console.log(`[MeshChat] URL: ${url}`);
console.log(
`[MeshChat] Model: ${modelId}, Provider: ${resolveProvider(modelId)}, Connection: ${modelProviderId ?? "none"}`,
);
console.log(`[MeshChat] Agent: id=${agentId ?? "none"}, mode=${agentMode}`);
console.log(
`[MeshChat] Messages: ${messages.length}, timeout: ${timeoutMs}ms`,
);
console.log(
`[MeshChat] Effective mesh URL: ${effectiveMeshUrl} (original: ${meshUrl})`,
);
messages.forEach((m, i) => {
const partsDesc = m.parts
.map((p) =>
p.type === "text"
? `text(${(p as any).text?.length ?? 0})`
: `file(${(p as any).filename ?? "?"})`,
)
.join(", ");
console.log(
`[MeshChat] Message[${i}]: role=${m.role}, parts=[${partsDesc}]`,
);
});

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);

const fetchStartTime = Date.now();
try {
console.log(`[MeshChat] Sending request to Decopilot API...`);
const response = await fetch(url, {
method: "POST",
headers: {
Expand All @@ -86,8 +113,18 @@ export async function callDecopilotAPI(
signal: controller.signal,
});

console.log(
`[MeshChat] Decopilot API response: status=${response.status}, ok=${response.ok}, time=${Date.now() - fetchStartTime}ms`,
);
console.log(
`[MeshChat] Response headers: content-type=${response.headers.get("content-type")}`,
);

if (!response.ok) {
const errorText = await response.text();
console.error(
`[MeshChat] Decopilot API FAILED: status=${response.status}, body length=${errorText.length}`,
);
throw new Error(
`Decopilot API call failed (${response.status}): ${errorText}`,
);
Expand Down
30 changes: 25 additions & 5 deletions shared/mesh-chat/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,24 +106,31 @@ export async function generateResponse(
): Promise<string> {
const apiMessages = messagesToPrompt(messages, config.systemPrompt);

console.log(
"[MeshChat] ========== generateResponse (non-streaming) ==========",
);
console.log("[MeshChat] Generating response", {
messageCount: apiMessages.length,
modelId: config.modelId,
hasAgent: !!config.agentId,
agentId: config.agentId,
meshUrl: config.meshUrl,
});

console.log({config, apiMessages});
const startTime = Date.now();
const response = await callDecopilotAPI(config, apiMessages);
console.log({response});

if (!response.body) {
console.error("[MeshChat] No response body from Decopilot API!");
throw new Error("No response body from Decopilot API");
}

console.log(`[MeshChat] Collecting stream text...`);
const text = await collectFullStreamText(response.body);

console.log(`[MeshChat] Response received (${text.length} chars)`);

console.log(
`[MeshChat] Response fully collected in ${Date.now() - startTime}ms (${text.length} chars)`,
);
return text || "Desculpe, não consegui gerar uma resposta.";
}

Expand All @@ -141,17 +148,30 @@ export async function generateResponseWithStreaming(
): Promise<string> {
const apiMessages = messagesToPrompt(messages, config.systemPrompt);

console.log("[MeshChat] ========== generateResponseWithStreaming ==========");
console.log("[MeshChat] Generating response (streaming)", {
messageCount: apiMessages.length,
modelId: config.modelId,
hasAgent: !!config.agentId,
agentId: config.agentId,
meshUrl: config.meshUrl,
});

const startTime = Date.now();
const response = await callDecopilotAPI(config, apiMessages);

if (!response.body) {
console.error(
"[MeshChat] No response body from Decopilot API (streaming)!",
);
throw new Error("No response body from Decopilot API");
}

return processStreamWithCallback(response.body, onStream);
console.log(`[MeshChat] Starting stream processing...`);
const result = await processStreamWithCallback(response.body, onStream);
console.log(
`[MeshChat] Streaming completed in ${Date.now() - startTime}ms (${result.length} chars)`,
);

return result;
}
Loading
Loading