| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { and, desc, eq, isNull } from "drizzle-orm"; |
| import { createHash } from "node:crypto"; |
| import { |
| db, |
| toolNetworks, |
| networkVersions, |
| networkPromotions, |
| problemClasses, |
| networkDispatchViolations, |
| type ToolNetworkRow, |
| type NetworkVersionRow, |
| type ProblemClassRow, |
| } from "@workspace/db"; |
| import { newId } from "./ids"; |
| import { logger } from "./logger"; |
| import type { LlmTool } from "../llm/types"; |
| import { validate as ajvValidate } from "@workspace/networks"; |
| import { |
| executeByGraph, |
| toNetworkRunResult, |
| type StepHandler, |
| } from "./tool-network/graph-executor.js"; |
|
|
| |
| |
| |
|
|
| export interface NetworkInternalGraph { |
| |
| nodes: string[]; |
| |
| edges?: Array<{ from: string; to: string; relation?: string }>; |
| } |
|
|
| export interface NetworkRuntimeContext { |
| conversationId?: string | null; |
| messageId?: string | null; |
| ownerUserId?: string | null; |
| |
| planId?: string | null; |
| |
| meta?: Record<string, unknown>; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| signal?: AbortSignal; |
| } |
|
|
| export interface NetworkRunResult { |
| output: Record<string, unknown>; |
| |
| steps: Array<{ |
| name: string; |
| durationMs: number; |
| status: "ok" | "error"; |
| summary?: string; |
| }>; |
| |
| artifactPath?: string | null; |
| |
| durationMs: number; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| metrics?: { |
| runnerScore?: number; |
| channelBreakdown?: Record<string, number>; |
| [k: string]: unknown; |
| }; |
| } |
|
|
| export type NetworkRunner = ( |
| input: Record<string, unknown>, |
| variant: NetworkVersionRow, |
| ctx: NetworkRuntimeContext, |
| ) => Promise<NetworkRunResult>; |
|
|
| const runners = new Map<string, NetworkRunner>(); |
|
|
| |
| |
| |
| |
| export function registerNetworkRunner(name: string, runner: NetworkRunner): void { |
| if (runners.has(name)) { |
| logger.warn({ name }, "tool-network: runner re-registered (overwriting)"); |
| } |
| runners.set(name, runner); |
| } |
|
|
| export function listRegisteredRunners(): string[] { |
| return [...runners.keys()].sort(); |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const graphStepHandlers = new Map<string, StepHandler>(); |
|
|
| export function registerGraphStepHandler(name: string, handler: StepHandler): void { |
| if (graphStepHandlers.has(name)) { |
| logger.warn( |
| { name }, |
| "tool-network: graph step handler re-registered (overwriting)", |
| ); |
| } |
| graphStepHandlers.set(name, handler); |
| } |
|
|
| export function listRegisteredGraphStepHandlers(): string[] { |
| return [...graphStepHandlers.keys()].sort(); |
| } |
|
|
| export function clearGraphStepHandlersForTest(): void { |
| graphStepHandlers.clear(); |
| } |
|
|
| function isGraphExecutorEnabled(): boolean { |
| const v = process.env["DOATLAS_GRAPH_EXECUTOR_ENABLED"]; |
| if (v === undefined) return true; |
| return v !== "0" && v.toLowerCase() !== "false"; |
| } |
|
|
| function isInputContractEnforced(): boolean { |
| const v = process.env["DOATLAS_INPUT_CONTRACT_ENFORCE"]; |
| if (v === undefined) { |
| |
| return process.env["NODE_ENV"] !== "production"; |
| } |
| return v !== "0" && v.toLowerCase() !== "false"; |
| } |
|
|
| |
| |
| |
|
|
| export interface UpsertProblemClassInput { |
| path: string; |
| parentPath?: string | null; |
| label: string; |
| description?: string; |
| capabilityTags?: string[]; |
| |
| |
| |
| |
| |
| |
| |
| |
| reviewerWeights?: Record<string, unknown> | null; |
| } |
|
|
| export async function upsertProblemClass( |
| input: UpsertProblemClassInput, |
| ): Promise<ProblemClassRow> { |
| const existing = await db |
| .select() |
| .from(problemClasses) |
| .where(eq(problemClasses.path, input.path)) |
| .limit(1); |
| if (existing[0]) { |
| const [updated] = await db |
| .update(problemClasses) |
| .set({ |
| parentPath: input.parentPath ?? existing[0].parentPath, |
| label: input.label, |
| description: input.description ?? existing[0].description, |
| capabilityTags: input.capabilityTags ?? (existing[0].capabilityTags as string[]), |
| reviewerWeights: input.reviewerWeights ?? existing[0].reviewerWeights, |
| updatedAt: new Date(), |
| }) |
| .where(eq(problemClasses.id, existing[0].id)) |
| .returning(); |
| return updated!; |
| } |
| const [inserted] = await db |
| .insert(problemClasses) |
| .values({ |
| id: newId("pcls"), |
| path: input.path, |
| parentPath: input.parentPath ?? null, |
| label: input.label, |
| description: input.description ?? "", |
| capabilityTags: input.capabilityTags ?? [], |
| reviewerWeights: input.reviewerWeights ?? null, |
| }) |
| .returning(); |
| return inserted!; |
| } |
|
|
| export async function listProblemClasses(): Promise<ProblemClassRow[]> { |
| return db.select().from(problemClasses); |
| } |
|
|
| export async function getProblemClassByPath( |
| path: string, |
| ): Promise<ProblemClassRow | null> { |
| const rows = await db |
| .select() |
| .from(problemClasses) |
| .where(eq(problemClasses.path, path)) |
| .limit(1); |
| return rows[0] ?? null; |
| } |
|
|
| |
| |
| |
|
|
| export interface UpsertNetworkInput { |
| name: string; |
| problemClassPath: string; |
| description?: string; |
| inputContract: Record<string, unknown>; |
| outputContract: Record<string, unknown>; |
| internalGraph: NetworkInternalGraph; |
| capabilityTags?: string[]; |
| costHint?: number | null; |
| latencyHintMs?: number | null; |
| builderModelTier?: "weak" | "strong"; |
| legacyAliasNodeId?: string | null; |
| |
| |
| |
| |
| initialVariant?: { |
| versionLabel: string; |
| config: Record<string, unknown>; |
| }; |
| } |
|
|
| |
| |
| |
| |
| export async function upsertNetwork( |
| input: UpsertNetworkInput, |
| ): Promise<ToolNetworkRow> { |
| const existing = await db |
| .select() |
| .from(toolNetworks) |
| .where(eq(toolNetworks.name, input.name)) |
| .limit(1); |
|
|
| let row: ToolNetworkRow; |
| if (existing[0]) { |
| const [updated] = await db |
| .update(toolNetworks) |
| .set({ |
| problemClassPath: input.problemClassPath, |
| description: input.description ?? existing[0].description, |
| inputContract: input.inputContract, |
| outputContract: input.outputContract, |
| internalGraph: input.internalGraph, |
| capabilityTags: input.capabilityTags ?? (existing[0].capabilityTags as string[]), |
| costHint: input.costHint ?? existing[0].costHint, |
| latencyHintMs: input.latencyHintMs ?? existing[0].latencyHintMs, |
| builderModelTier: input.builderModelTier ?? existing[0].builderModelTier, |
| legacyAliasNodeId: input.legacyAliasNodeId ?? existing[0].legacyAliasNodeId, |
| updatedAt: new Date(), |
| }) |
| .where(eq(toolNetworks.id, existing[0].id)) |
| .returning(); |
| row = updated!; |
| } else { |
| const [inserted] = await db |
| .insert(toolNetworks) |
| .values({ |
| id: newId("tnet"), |
| name: input.name, |
| problemClassPath: input.problemClassPath, |
| description: input.description ?? "", |
| inputContract: input.inputContract, |
| outputContract: input.outputContract, |
| internalGraph: input.internalGraph, |
| capabilityTags: input.capabilityTags ?? [], |
| costHint: input.costHint ?? null, |
| latencyHintMs: input.latencyHintMs ?? null, |
| builderModelTier: input.builderModelTier ?? "strong", |
| legacyAliasNodeId: input.legacyAliasNodeId ?? null, |
| status: "active", |
| }) |
| .returning(); |
| row = inserted!; |
| } |
|
|
| if (!row.activeVariantId && input.initialVariant) { |
| const variant = await createVariant({ |
| networkId: row.id, |
| versionLabel: input.initialVariant.versionLabel, |
| config: input.initialVariant.config, |
| internalGraph: input.internalGraph, |
| builderModelTier: input.builderModelTier ?? "strong", |
| promote: true, |
| reason: "initial_seed", |
| }); |
| row = { ...row, activeVariantId: variant.id }; |
| } |
| return row; |
| } |
|
|
| export async function getNetworkByName( |
| name: string, |
| ): Promise<ToolNetworkRow | null> { |
| const rows = await db |
| .select() |
| .from(toolNetworks) |
| .where(eq(toolNetworks.name, name)) |
| .limit(1); |
| return rows[0] ?? null; |
| } |
|
|
| export async function listActiveNetworks(): Promise<ToolNetworkRow[]> { |
| return db |
| .select() |
| .from(toolNetworks) |
| .where(eq(toolNetworks.status, "active")); |
| } |
|
|
| export async function listNetworksForClass( |
| problemClassPath: string, |
| ): Promise<ToolNetworkRow[]> { |
| return db |
| .select() |
| .from(toolNetworks) |
| .where( |
| and( |
| eq(toolNetworks.problemClassPath, problemClassPath), |
| eq(toolNetworks.status, "active"), |
| ), |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function getActiveVariant( |
| network: ToolNetworkRow, |
| userId?: string | null, |
| ): Promise<NetworkVersionRow | null> { |
| if (userId) { |
| const privateRows = await db |
| .select() |
| .from(networkVersions) |
| .where( |
| and( |
| eq(networkVersions.networkId, network.id), |
| eq(networkVersions.privateNamespace, userId), |
| eq(networkVersions.status, "active"), |
| ), |
| ) |
| .limit(1); |
| if (privateRows[0]) return privateRows[0]; |
| } |
| if (!network.activeVariantId) return null; |
| const rows = await db |
| .select() |
| .from(networkVersions) |
| .where(eq(networkVersions.id, network.activeVariantId)) |
| .limit(1); |
| return rows[0] ?? null; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function getOrCreateUserPrivateVariant( |
| networkId: string, |
| userId: string, |
| reason: string, |
| ): Promise<NetworkVersionRow> { |
| if (!userId) { |
| throw new Error("getOrCreateUserPrivateVariant: userId required"); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| for (let attempt = 0; attempt < 2; attempt += 1) { |
| const existing = await db |
| .select() |
| .from(networkVersions) |
| .where( |
| and( |
| eq(networkVersions.networkId, networkId), |
| eq(networkVersions.privateNamespace, userId), |
| eq(networkVersions.status, "active"), |
| ), |
| ) |
| .limit(1); |
| if (existing[0]) return existing[0]; |
|
|
| const [network] = await db |
| .select() |
| .from(toolNetworks) |
| .where(eq(toolNetworks.id, networkId)) |
| .limit(1); |
| if (!network) { |
| throw new Error( |
| `getOrCreateUserPrivateVariant: network ${networkId} not found`, |
| ); |
| } |
| if (!network.activeVariantId) { |
| throw new Error( |
| `getOrCreateUserPrivateVariant: network ${networkId} has no public active variant to clone from`, |
| ); |
| } |
| const [publicActive] = await db |
| .select() |
| .from(networkVersions) |
| .where(eq(networkVersions.id, network.activeVariantId)) |
| .limit(1); |
| if (!publicActive) { |
| throw new Error( |
| `getOrCreateUserPrivateVariant: public active variant ${network.activeVariantId} not found`, |
| ); |
| } |
| const versionLabel = `${publicActive.versionLabel}-priv-${userId.slice(0, 8)}-${Date.now()}-${attempt}`; |
| try { |
| const [inserted] = await db |
| .insert(networkVersions) |
| .values({ |
| id: newId("nver"), |
| networkId, |
| versionLabel, |
| internalGraph: publicActive.internalGraph, |
| config: publicActive.config, |
| status: "active", |
| builtBy: `user:${userId}`, |
| builderModelTier: publicActive.builderModelTier, |
| privateNamespace: userId, |
| }) |
| .returning(); |
| logger.info( |
| { networkId, userId, fromVariantId: publicActive.id, newVariantId: inserted!.id, reason }, |
| "tool-network: created user-private variant", |
| ); |
| return inserted!; |
| } catch (err) { |
| |
| |
| |
| const code = (err as { code?: string }).code; |
| if (code !== "23505" || attempt >= 1) throw err; |
| logger.info( |
| { networkId, userId, reason }, |
| "tool-network: race detected on user-private variant, retrying select", |
| ); |
| } |
| } |
| |
| |
| throw new Error( |
| "getOrCreateUserPrivateVariant: race resolution failed after retry", |
| ); |
| } |
|
|
| export interface CreateVariantInput { |
| networkId: string; |
| versionLabel: string; |
| config: Record<string, unknown>; |
| internalGraph: NetworkInternalGraph; |
| builderModelTier: "weak" | "strong"; |
| promote: boolean; |
| reason: string; |
| } |
|
|
| export async function createVariant( |
| input: CreateVariantInput, |
| ): Promise<NetworkVersionRow> { |
| |
| const existing = await db |
| .select() |
| .from(networkVersions) |
| .where( |
| and( |
| eq(networkVersions.networkId, input.networkId), |
| eq(networkVersions.versionLabel, input.versionLabel), |
| ), |
| ) |
| .limit(1); |
| let variant: NetworkVersionRow; |
| if (existing[0]) { |
| variant = existing[0]; |
| } else { |
| const [inserted] = await db |
| .insert(networkVersions) |
| .values({ |
| id: newId("nver"), |
| networkId: input.networkId, |
| versionLabel: input.versionLabel, |
| internalGraph: input.internalGraph, |
| config: input.config, |
| status: input.promote ? "active" : "shadow", |
| builtBy: "system", |
| builderModelTier: input.builderModelTier, |
| }) |
| .returning(); |
| variant = inserted!; |
| } |
| if (input.promote) { |
| await promoteVariant(input.networkId, variant.id, input.reason); |
| } |
| return variant; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function promoteVariant( |
| networkId: string, |
| toVariantId: string, |
| reason: string, |
| ): Promise<void> { |
| try { |
| await _promoteVariantTx(networkId, toVariantId, reason); |
| } catch (err) { |
| |
| |
| |
| |
| try { |
| const { capabilityLifecycleEvents } = await import("@workspace/db"); |
| await db.insert(capabilityLifecycleEvents).values({ |
| id: newId("clcyev"), |
| capabilityId: `network:${networkId}`, |
| fromState: null, |
| toState: "promotion_failed", |
| reason: "promotion_failed", |
| payload: { |
| networkId, |
| toVariantId, |
| requestedReason: reason, |
| error: err instanceof Error ? err.message : String(err), |
| }, |
| }); |
| } catch (auditErr) { |
| logger.error( |
| { auditErr, originalErr: err, networkId, toVariantId }, |
| "tool-network: failed to write promotion_failed audit (original promotion already rolled back)", |
| ); |
| } |
| throw err; |
| } |
| } |
|
|
| async function _promoteVariantTx( |
| networkId: string, |
| toVariantId: string, |
| reason: string, |
| ): Promise<void> { |
| await db.transaction(async (tx) => { |
| const [network] = await tx |
| .select() |
| .from(toolNetworks) |
| .where(eq(toolNetworks.id, networkId)) |
| .limit(1); |
| if (!network) throw new Error(`tool-network: ${networkId} not found`); |
| if (network.activeVariantId === toVariantId) return; |
|
|
| |
| const [target] = await tx |
| .select() |
| .from(networkVersions) |
| .where(eq(networkVersions.id, toVariantId)) |
| .limit(1); |
| if (!target) { |
| throw new Error( |
| `tool-network: variant ${toVariantId} not found (cannot promote)`, |
| ); |
| } |
| if (target.privateNamespace) { |
| throw new Error( |
| `tool-network: variant ${toVariantId} is user-private (namespace=${target.privateNamespace}); ` + |
| `cannot promote to global active slot`, |
| ); |
| } |
| if (target.networkId !== networkId) { |
| throw new Error( |
| `tool-network: variant ${toVariantId} belongs to network ${target.networkId}, not ${networkId}`, |
| ); |
| } |
|
|
| await tx.insert(networkPromotions).values({ |
| id: newId("nprm"), |
| networkId, |
| fromVariantId: network.activeVariantId, |
| toVariantId, |
| reason, |
| decidedBy: "system", |
| }); |
| await tx |
| .update(networkVersions) |
| .set({ status: "active" }) |
| .where(eq(networkVersions.id, toVariantId)); |
| if (network.activeVariantId) { |
| |
| |
| |
| await tx |
| .update(networkVersions) |
| .set({ status: "shadow" }) |
| .where( |
| and( |
| eq(networkVersions.id, network.activeVariantId), |
| isNull(networkVersions.privateNamespace), |
| ), |
| ); |
| } |
| await tx |
| .update(toolNetworks) |
| .set({ activeVariantId: toVariantId, updatedAt: new Date() }) |
| .where(eq(toolNetworks.id, networkId)); |
| }); |
| } |
|
|
| export async function listPromotions( |
| networkId: string, |
| limit = 20, |
| ): Promise<Array<typeof networkPromotions.$inferSelect>> { |
| return db |
| .select() |
| .from(networkPromotions) |
| .where(eq(networkPromotions.networkId, networkId)) |
| .orderBy(desc(networkPromotions.createdAt)) |
| .limit(limit); |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| export function networkToLlmTool(network: ToolNetworkRow): LlmTool { |
| const params = |
| typeof network.inputContract === "object" && network.inputContract |
| ? (network.inputContract as Record<string, unknown>) |
| : { type: "object", properties: {}, additionalProperties: true }; |
| const hintParts: string[] = [`problem_class=${network.problemClassPath}`]; |
| if (network.costHint != null) hintParts.push(`cost~${network.costHint}`); |
| if (network.latencyHintMs != null) hintParts.push(`latency~${network.latencyHintMs}ms`); |
| return { |
| name: `run_${network.name}`, |
| description: |
| (network.description || `Run the ${network.name} tool network.`) + |
| `\n[${hintParts.join(", ")}]`, |
| parameters: params, |
| }; |
| } |
|
|
| |
| |
| |
|
|
| export interface RunNetworkArgs { |
| networkName: string; |
| input: Record<string, unknown>; |
| ctx?: NetworkRuntimeContext; |
| |
| |
| |
| |
| |
| |
| variantOverride?: string; |
| |
| |
| |
| |
| |
| actor?: string; |
| |
| |
| |
| |
| |
| |
| |
| |
| enableShadow?: boolean; |
| |
| |
| |
| |
| |
| shadowSampleRate?: number; |
| } |
|
|
| export async function runNetwork(args: RunNetworkArgs): Promise<NetworkRunResult> { |
| const network = await getNetworkByName(args.networkName); |
| if (!network) { |
| throw new Error(`tool-network: '${args.networkName}' not registered`); |
| } |
| let variant: NetworkVersionRow | null = null; |
| if (args.variantOverride) { |
| const rows = await db |
| .select() |
| .from(networkVersions) |
| .where(eq(networkVersions.id, args.variantOverride)) |
| .limit(1); |
| variant = rows[0] ?? null; |
| if (variant && variant.networkId !== network.id) { |
| throw new Error( |
| `tool-network: variantOverride ${args.variantOverride} does not belong to ${args.networkName}`, |
| ); |
| } |
| } else { |
| |
| variant = await getActiveVariant(network, args.ctx?.ownerUserId ?? null); |
| } |
| if (!variant) { |
| throw new Error( |
| `tool-network: '${args.networkName}' has no active variant`, |
| ); |
| } |
| const start = Date.now(); |
| const ctx: NetworkRuntimeContext = args.ctx ?? { |
| meta: args.actor ? { actor: args.actor } : {}, |
| }; |
|
|
| |
| |
| |
| |
| const variantConfig = (variant.config ?? {}) as Record<string, unknown>; |
| const useGraph = |
| variantConfig["executor"] === "graph" && isGraphExecutorEnabled(); |
|
|
| |
| |
| |
| const runner = useGraph ? null : runners.get(network.name); |
| let result: NetworkRunResult; |
| if (useGraph) { |
| const graph = variant.internalGraph as |
| | { nodes?: string[]; edges?: Array<{ from: string; to: string; relation?: string }> } |
| | null; |
| if (!graph || !Array.isArray(graph.nodes)) { |
| throw new Error( |
| `tool-network: '${args.networkName}' variant ${variant.id} has executor='graph' ` + |
| `but internalGraph.nodes is missing`, |
| ); |
| } |
| |
| |
| |
| const handlers: Record<string, StepHandler> = {}; |
| for (const name of graph.nodes) { |
| const h = graphStepHandlers.get(name); |
| if (h) handlers[name] = h; |
| } |
| try { |
| const ge = await executeByGraph({ |
| graph: { nodes: graph.nodes, edges: graph.edges }, |
| input: args.input, |
| ctx, |
| handlers, |
| }); |
| result = toNetworkRunResult(ge); |
| result.durationMs = result.durationMs ?? Date.now() - start; |
| } catch (err) { |
| logger.error( |
| { err, network: network.name, variantId: variant.id, executor: "graph" }, |
| "tool-network: graph executor threw", |
| ); |
| throw err; |
| } |
| } else { |
| if (!runner) { |
| throw new Error( |
| `tool-network: '${args.networkName}' runner not registered (call registerNetworkRunner at boot)`, |
| ); |
| } |
| try { |
| const r = await runner(args.input, variant, ctx); |
| result = { |
| ...r, |
| durationMs: r.durationMs ?? Date.now() - start, |
| }; |
| } catch (err) { |
| logger.error( |
| { err, network: network.name, variantId: variant.id }, |
| "tool-network: runner threw", |
| ); |
| throw err; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| if (args.enableShadow && !args.variantOverride && runner) { |
| |
| |
| |
| void import("./evolution/shadow") |
| .then(({ maybeRecordShadowFromActive }) => |
| maybeRecordShadowFromActive({ |
| network, |
| activeVariant: variant!, |
| input: args.input, |
| ctx, |
| activeResult: result, |
| runner, |
| sampleRate: args.shadowSampleRate, |
| }), |
| ) |
| .catch((err) => |
| logger.warn( |
| { err, network: network.name }, |
| "tool-network: shadow sampling threw (suppressed)", |
| ), |
| ); |
| } |
| return result; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function runNetworkForChat( |
| args: Omit<RunNetworkArgs, "enableShadow">, |
| ): Promise<NetworkRunResult> { |
| return runNetwork({ |
| ...args, |
| actor: args.actor ?? "chat", |
| enableShadow: true, |
| }); |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| export const NETWORK_TOOL_PREFIX = "run_"; |
|
|
| export interface DispatchNetworkToolResult { |
| |
| matched: boolean; |
| |
| result?: NetworkRunResult; |
| |
| error?: { error: string; error_code: string; retryable: boolean }; |
| |
| durationMs?: number; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function dispatchNetworkTool( |
| toolName: string, |
| args: Record<string, unknown>, |
| ctx?: NetworkRuntimeContext, |
| |
| |
| |
| |
| |
| shadowSampleRate?: number, |
| ): Promise<DispatchNetworkToolResult> { |
| if (!toolName.startsWith(NETWORK_TOOL_PREFIX)) { |
| return { matched: false }; |
| } |
| const networkName = toolName.slice(NETWORK_TOOL_PREFIX.length); |
| const network = await getNetworkByName(networkName).catch(() => null); |
| if (!network || !network.activeVariantId) { |
| return { matched: false }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| const enforce = isInputContractEnforced(); |
| const contract = network.inputContract as Record<string, unknown> | null; |
| if (contract && typeof contract === "object") { |
| const v = ajvValidate(contract, args); |
| if (!v.valid) { |
| const argsHash = createHash("sha256") |
| .update(JSON.stringify(args ?? {})) |
| .digest("hex") |
| .slice(0, 16); |
| try { |
| await db.insert(networkDispatchViolations).values({ |
| id: newId("ndv"), |
| networkName, |
| toolName, |
| argsHash, |
| detailsJson: { errors: v.errors, contractTitle: contract["title"] ?? null }, |
| mode: enforce ? "enforced" : "observed_only", |
| conversationId: ctx?.conversationId ?? null, |
| ownerUserId: ctx?.ownerUserId ?? null, |
| }); |
| } catch (err) { |
| |
| logger.warn( |
| { err, networkName }, |
| "tool-network: failed to persist dispatch violation", |
| ); |
| } |
| if (enforce) { |
| return { |
| matched: true, |
| error: { |
| error: `INPUT_CONTRACT_VIOLATION: ${v.errors |
| .map((e) => `${e.path}: ${e.message}`) |
| .join("; ")}`, |
| error_code: "INPUT_CONTRACT_VIOLATION", |
| retryable: false, |
| }, |
| durationMs: 0, |
| }; |
| } |
| |
| logger.warn( |
| { networkName, errors: v.errors }, |
| "tool-network: input_contract violation observed but not enforced", |
| ); |
| } |
| } |
|
|
| const start = Date.now(); |
| try { |
| const result = await runNetworkForChat({ |
| networkName, |
| input: args, |
| ctx, |
| shadowSampleRate, |
| }); |
| return { matched: true, result, durationMs: Date.now() - start }; |
| } catch (err) { |
| return { |
| matched: true, |
| error: { |
| error: err instanceof Error ? err.message : String(err), |
| error_code: "network_runner_threw", |
| retryable: false, |
| }, |
| durationMs: Date.now() - start, |
| }; |
| } |
| } |
|
|