Skip to content
74 changes: 73 additions & 1 deletion packages/opencode/src/altimate/native/schema-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,82 @@
*
* Translates the bridge protocol's `schema_path` / `schema_context` parameters
* into altimate-core `Schema` objects.
*
* Tools pass `schema_context` in two possible formats:
*
* 1. **Flat format** (used by most tools):
* `{ "table_name": { "col_name": "TYPE", ... } }`
*
* 2. **SchemaDefinition format** (matches Rust struct):
* `{ "tables": { "table_name": { "columns": [{ "name": "col", "type": "TYPE" }] } } }`
*
* This module normalizes both formats into the SchemaDefinition format
* expected by `Schema.fromJson()`.
*/

import { Schema } from "@altimateai/altimate-core"

/**
* Detect whether a schema_context object is in flat format or SchemaDefinition format.
*
* Flat format: `{ "table_name": { "col_name": "TYPE" } }`
* SchemaDefinition format: has a `tables` key with nested structure.
*/
function isSchemaDefinitionFormat(ctx: Record<string, any>): boolean {
return "tables" in ctx && typeof ctx.tables === "object" && ctx.tables !== null
}
Comment thread
anandgupta42 marked this conversation as resolved.

/**
* Convert flat schema format to SchemaDefinition format.
*
* Handles three input variants:
*
* 1. Flat map: `{ "customers": { "id": "INTEGER", "name": "VARCHAR" } }`
* 2. Array form: `{ "customers": [{ "name": "id", "data_type": "INTEGER" }] }`
* 3. Partial SD: `{ "customers": { "columns": [{ "name": "id", "type": "INTEGER" }] } }`
*
* Output: `{ "tables": { "customers": { "columns": [{ "name": "id", "type": "INTEGER" }, ...] } } }`
*/
function flatToSchemaDefinition(flat: Record<string, any>): Record<string, any> {
const tables: Record<string, any> = {}
for (const [tableName, colsOrDef] of Object.entries(flat)) {
if (colsOrDef === null || colsOrDef === undefined) continue

// Variant 2: array of column definitions
if (Array.isArray(colsOrDef)) {
const columns = colsOrDef.map((c: any) => ({
name: c.name,
type: c.type ?? c.data_type ?? "VARCHAR",
}))
tables[tableName] = { columns }
} else if (typeof colsOrDef === "object") {
// Variant 3: already has a `columns` array
if (Array.isArray(colsOrDef.columns)) {
tables[tableName] = colsOrDef
} else {
// Variant 1: flat map { "col_name": "TYPE", ... }
const columns = Object.entries(colsOrDef).map(([colName, colType]) => ({
name: colName,
type: String(colType),
}))
tables[tableName] = { columns }
}
}
}
return { tables }
}

/**
* Normalize a schema_context into SchemaDefinition JSON format.
* Accepts both flat and SchemaDefinition formats.
*/
function normalizeSchemaContext(ctx: Record<string, any>): string {
if (isSchemaDefinitionFormat(ctx)) {
return JSON.stringify(ctx)
}
return JSON.stringify(flatToSchemaDefinition(ctx))
}

/**
* Resolve a Schema from a file path or inline JSON context.
* Returns null when neither source is provided.
Expand All @@ -19,7 +91,7 @@ export function resolveSchema(
return Schema.fromFile(schemaPath)
}
if (schemaContext && Object.keys(schemaContext).length > 0) {
return Schema.fromJson(JSON.stringify(schemaContext))
return Schema.fromJson(normalizeSchemaContext(schemaContext))
}
return null
}
Expand Down
6 changes: 4 additions & 2 deletions packages/opencode/src/altimate/native/sql/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,10 @@ register("sql.rewrite", async (params) => {
rewritten_sql: result.suggestions?.[0]?.rewritten_sql ?? null,
rewrites_applied: result.suggestions?.map((s: any) => ({
rule: s.rule,
description: s.explanation,
rewritten_sql: s.rewritten_sql,
original_fragment: params.sql,
rewritten_fragment: s.rewritten_sql ?? params.sql,
explanation: s.explanation ?? s.improvement ?? "",
can_auto_apply: (s.confidence ?? 0) >= 0.7,
})) ?? [],
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,26 @@ export const AltimateCoreColumnLineageTool = Tool.define("altimate_core_column_l

function formatColumnLineage(data: Record<string, any>): string {
if (data.error) return `Error: ${data.error}`
if (!data.column_lineage?.length) return "No column lineage edges found."
const lines = ["Column lineage:\n"]
for (const edge of data.column_lineage) {
lines.push(` ${edge.source} -> ${edge.target}${edge.transform ? ` (${edge.transform})` : ""}`)
if (!data.column_lineage?.length && !data.column_dict) return "No column lineage edges found."
const lines: string[] = []

// column_dict: output columns -> source columns mapping
if (data.column_dict && Object.keys(data.column_dict).length > 0) {
lines.push("Column Mappings:")
for (const [target, sources] of Object.entries(data.column_dict)) {
const srcList = Array.isArray(sources) ? (sources as string[]).join(", ") : JSON.stringify(sources)
lines.push(` ${target} ← ${srcList}`)
}
lines.push("")
}

if (data.column_lineage?.length) {
lines.push("Lineage Edges:")
for (const edge of data.column_lineage) {
const transform = edge.lens_type ?? edge.transform_type ?? edge.transform ?? ""
lines.push(` ${edge.source} → ${edge.target}${transform ? ` (${transform})` : ""}`)
}
}
return lines.join("\n")

return lines.length ? lines.join("\n") : "No column lineage edges found."
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const AltimateCoreCompleteTool = Tool.define("altimate_core_complete", {
schema_context: args.schema_context,
})
const data = result.data as Record<string, any>
const count = data.suggestions?.length ?? 0
const count = data.items?.length ?? data.suggestions?.length ?? 0
return {
title: `Complete: ${count} suggestion(s)`,
metadata: { success: result.success, suggestion_count: count },
Expand All @@ -35,9 +35,10 @@ export const AltimateCoreCompleteTool = Tool.define("altimate_core_complete", {

function formatComplete(data: Record<string, any>): string {
if (data.error) return `Error: ${data.error}`
if (!data.suggestions?.length) return "No completions available."
const items = data.items ?? data.suggestions ?? []
if (!items.length) return "No completions available."
const lines = ["Suggestions:\n"]
for (const s of data.suggestions) {
for (const s of items) {
lines.push(` ${s.label ?? s.text} (${s.kind ?? s.type ?? "unknown"})`)
}
return lines.join("\n")
Expand Down
16 changes: 9 additions & 7 deletions packages/opencode/src/altimate/tools/altimate-core-fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,23 @@ export const AltimateCoreFixTool = Tool.define("altimate_core_fix", {
function formatFix(data: Record<string, any>): string {
if (data.error) return `Error: ${data.error}`
const lines: string[] = []
if (data.fixed_sql) {
if (data.fixed && data.fixed_sql) {
lines.push("Fixed SQL:")
lines.push(data.fixed_sql)
if (data.changes?.length) {
const fixes = data.fixes_applied ?? data.changes ?? []
if (fixes.length) {
lines.push("\nChanges applied:")
for (const c of data.changes) {
lines.push(` - ${c.description ?? c}`)
for (const c of fixes) {

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 0bf5bedc8. Changed the condition from data.fixed && data.fixed_sql to data.fixed_sql && data.fixed !== false — so a valid fixed_sql is shown when fixed is either true or absent (undefined). Only an explicit fixed: false suppresses the output.

lines.push(` - ${c.description ?? c.message ?? c}`)
}
}
} else {
lines.push("Could not auto-fix the SQL.")
if (data.errors?.length) {
const unfixable = data.unfixable_errors ?? data.errors ?? []
if (unfixable.length) {
lines.push("\nErrors found:")
for (const e of data.errors) {
lines.push(` - ${e.message ?? e}`)
for (const e of unfixable) {
lines.push(` - ${e.message ?? e.reason ?? e}`)
}
}
}
Expand Down
22 changes: 18 additions & 4 deletions packages/opencode/src/altimate/tools/altimate-core-grade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ export const AltimateCoreGradeTool = Tool.define("altimate_core_grade", {
schema_context: args.schema_context,
})
const data = result.data as Record<string, any>
const grade = data.overall_grade ?? data.grade
const score = data.scores?.overall != null ? Math.round(data.scores.overall * 100) : data.score
return {
title: `Grade: ${data.grade ?? "?"}`,
metadata: { success: result.success, grade: data.grade, score: data.score },
title: `Grade: ${grade ?? "?"}`,
metadata: { success: result.success, grade, score },
output: formatGrade(data),
}
} catch (e) {
Expand All @@ -33,8 +35,20 @@ export const AltimateCoreGradeTool = Tool.define("altimate_core_grade", {
function formatGrade(data: Record<string, any>): string {
if (data.error) return `Error: ${data.error}`
const lines: string[] = []
lines.push(`Grade: ${data.grade}`)
if (data.score != null) lines.push(`Score: ${data.score}/100`)
const grade = data.overall_grade ?? data.grade
lines.push(`Grade: ${grade}`)
const scores = data.scores
if (scores) {
const overall = scores.overall != null ? Math.round(scores.overall * 100) : null
if (overall != null) lines.push(`Score: ${overall}/100`)
lines.push("\nCategory scores:")
if (scores.syntax != null) lines.push(` syntax: ${Math.round(scores.syntax * 100)}/100`)
if (scores.style != null) lines.push(` style: ${Math.round(scores.style * 100)}/100`)
if (scores.safety != null) lines.push(` safety: ${Math.round(scores.safety * 100)}/100`)
if (scores.complexity != null) lines.push(` complexity: ${Math.round(scores.complexity * 100)}/100`)
} else if (data.score != null) {
lines.push(`Score: ${data.score}/100`)
}
if (data.categories) {
lines.push("\nCategory scores:")
for (const [cat, score] of Object.entries(data.categories)) {
Expand Down
16 changes: 10 additions & 6 deletions packages/opencode/src/altimate/tools/altimate-core-rewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const AltimateCoreRewriteTool = Tool.define("altimate_core_rewrite", {
schema_context: args.schema_context,
})
const data = result.data as Record<string, any>
const rewriteCount = data.rewrites?.length ?? (data.rewritten_sql && data.rewritten_sql !== args.sql ? 1 : 0)
const suggestions = data.suggestions ?? data.rewrites ?? []
const rewriteCount = suggestions.length || (data.rewritten_sql && data.rewritten_sql !== args.sql ? 1 : 0)
return {
title: `Rewrite: ${rewriteCount} suggestion(s)`,
metadata: { success: result.success, rewrite_count: rewriteCount },
Expand All @@ -33,19 +34,22 @@ export const AltimateCoreRewriteTool = Tool.define("altimate_core_rewrite", {

function formatRewrite(data: Record<string, any>): string {
if (data.error) return `Error: ${data.error}`
if (!data.rewrites?.length) {
const suggestions = data.suggestions ?? data.rewrites ?? []
if (!suggestions.length) {
if (data.rewritten_sql) return `Optimized SQL:\n${data.rewritten_sql}`
return "No rewrites suggested."
}
const lines: string[] = []
if (data.rewritten_sql) {
// Use first suggestion's rewritten_sql if top-level rewritten_sql not present
const bestSql = data.rewritten_sql ?? suggestions[0]?.rewritten_sql
if (bestSql) {
lines.push("Optimized SQL:")
lines.push(data.rewritten_sql)
lines.push(bestSql)
lines.push("")
}
lines.push("Rewrites applied:")
for (const r of data.rewrites) {
lines.push(` - ${r.rule ?? r.type}: ${r.explanation ?? r.description}`)
for (const r of suggestions) {
lines.push(` - ${r.rule ?? r.type}: ${r.explanation ?? r.description ?? r.improvement}`)
}
return lines.join("\n")
}
32 changes: 27 additions & 5 deletions packages/opencode/src/altimate/tools/altimate-core-schema-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ export const AltimateCoreSchemaDiffTool = Tool.define("altimate_core_schema_diff
})
const data = result.data as Record<string, any>
const changeCount = data.changes?.length ?? 0
const hasBreaking = data.has_breaking_changes ?? data.has_breaking ?? false
return {
title: `Schema Diff: ${changeCount} change(s)${data.has_breaking ? " (BREAKING)" : ""}`,
metadata: { success: result.success, change_count: changeCount, has_breaking: data.has_breaking },
title: `Schema Diff: ${changeCount} change(s)${hasBreaking ? " (BREAKING)" : ""}`,
metadata: { success: result.success, change_count: changeCount, has_breaking: hasBreaking },
output: formatSchemaDiff(data),
}
} catch (e) {
Expand All @@ -37,10 +38,31 @@ function formatSchemaDiff(data: Record<string, any>): string {
if (data.error) return `Error: ${data.error}`
if (!data.changes?.length) return "Schemas are identical."
const lines: string[] = []
if (data.has_breaking) lines.push("WARNING: Breaking changes detected!\n")
const hasBreaking = data.has_breaking_changes ?? data.has_breaking ?? false
if (hasBreaking) lines.push("WARNING: Breaking changes detected!\n")

// Rust SchemaChange uses tagged enum: { type: "column_added", table: "...", ... }
const breakingTypes = new Set(["table_removed", "column_removed", "column_type_changed"])
for (const c of data.changes) {
const marker = c.breaking ? "BREAKING" : c.severity ?? "info"
lines.push(` [${marker}] ${c.type}: ${c.description ?? c.message}`)
const isBreaking = breakingTypes.has(c.type) ||
(c.type === "nullability_changed" && c.old_nullable && !c.new_nullable)
const marker = isBreaking ? "BREAKING" : "info"
const desc = formatChange(c)
lines.push(` [${marker}] ${desc}`)
}

if (data.summary) lines.push(`\nSummary: ${data.summary}`)
return lines.join("\n")
}

function formatChange(c: Record<string, any>): string {
switch (c.type) {
case "table_added": return `Table '${c.table}' added`
case "table_removed": return `Table '${c.table}' removed`
case "column_added": return `Column '${c.table}.${c.column}' added (${c.data_type})`
case "column_removed": return `Column '${c.table}.${c.column}' removed`
case "column_type_changed": return `Column '${c.table}.${c.column}' type changed: ${c.old_type} → ${c.new_type}`
case "nullability_changed": return `Column '${c.table}.${c.column}' nullability: ${c.old_nullable ? "nullable" : "not null"} → ${c.new_nullable ? "nullable" : "not null"}`
default: return `${c.type}: ${c.description ?? c.message ?? JSON.stringify(c)}`
}
}
4 changes: 2 additions & 2 deletions packages/opencode/src/altimate/tools/sql-analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export const SqlAnalyzeTool = Tool.define("sql_analyze", {
})

function formatAnalysis(result: SqlAnalyzeResult): string {
if (!result.success) {
return `Analysis failed: ${result.error ?? "Unknown error"}`
if (result.error) {
return `Analysis failed: ${result.error}`
}

if (result.issues.length === 0) {
Expand Down
40 changes: 40 additions & 0 deletions packages/opencode/test/altimate/altimate-core-native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,46 @@ describe("Schema Resolution", () => {
})
expect(schema.tableNames()).toContain("orders")
})

test("resolveSchema from flat format (tool-style schema_context)", () => {
// This is the format most tools pass: { "table_name": { "col": "TYPE" } }
const ctx = {
customers: { customer_id: "INTEGER", name: "VARCHAR", email: "VARCHAR" },
orders: { order_id: "INTEGER", customer_id: "INTEGER", amount: "DECIMAL" },
}
const schema = resolveSchema(undefined, ctx)
expect(schema).not.toBeNull()
const tables = schema!.tableNames().sort()
expect(tables).toContain("customers")
expect(tables).toContain("orders")
expect(schema!.columnNames("customers")).toContain("customer_id")
expect(schema!.columnNames("customers")).toContain("email")
expect(schema!.columnNames("orders")).toContain("amount")
})

test("resolveSchema from array-of-columns format (lineage_check style)", () => {
// This is the format lineage_check uses: { "table": [{ name, data_type }] }
const ctx = {
users: [
{ name: "id", data_type: "INT" },
{ name: "email", data_type: "VARCHAR" },
],
}
const schema = resolveSchema(undefined, ctx)
expect(schema).not.toBeNull()
expect(schema!.tableNames()).toContain("users")
expect(schema!.columnNames("users")).toContain("id")
expect(schema!.columnNames("users")).toContain("email")
})

test("schemaOrEmpty handles flat format without falling back to empty", () => {
const schema = schemaOrEmpty(undefined, {
products: { id: "INT", name: "VARCHAR", price: "DECIMAL" },
})
const tables = schema.tableNames()
expect(tables).toContain("products")
expect(tables).not.toContain("_empty_")
})
})

// ---------------------------------------------------------------------------
Expand Down
Loading
Loading