mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-20 09:53:40 +02:00
The last open item from the plan. Missions can now draft invoices from chat context, mark customer payments, and read status for autonomous follow-up cadences. Tool catalog (packages/shared-ai/src/tools/schemas.ts) - create_invoice (propose) — clientName + lines[] + currency + due - mark_invoice_paid (propose) — by id, optional back-dated paidAt - list_invoices (auto) — with status + limit filter - get_invoice_stats (auto) — open/overdue/YTD per currency Had to widen the tool-parameter type vocabulary so create_invoice can declare lines as a typed array. Touched three places: - ToolSchema-side: the catalog's `type` string is already free-form so 'array' / 'object' just pass through - ModuleTool-side (apps/mana/apps/web/src/lib/data/tools/types.ts): added 'array' | 'object' to the union so TS doesn't narrow the executor's param signatures - function-schema translator (packages/shared-ai): mapParamType + JsonSchemaProperty both gained the two new types; the catalog-typo guard test now uses 'fruit' as its sentinel (array no longer unknown) Executor (apps/mana/apps/web/src/lib/modules/invoices/tools.ts) - coerceLines accepts either a real array or a JSON-stringified array (planners vary), skips malformed entries, converts major→minor units - create_invoice pulls the generated number back from Dexie so the success message shows "Entwurf 2026-0042 …" — the user recognises it - mark_invoice_paid normalises YYYY-MM-DD → ISO so the store's timestamp invariant (ISO throughout) stays intact - list_invoices derives overdue on read (consistent with useAllInvoices), returns major-unit amounts so the LLM reasons in user-facing numbers - get_invoice_stats returns counts + open/overdue/YTD per currency Registration: invoicesTools added to tools/init.ts. mana-ai drift guard is happy (41/41 green); webapp + shared-ai type-check 0 errors; full invoice test suite 59/59 green. Closes: docs/plans/invoices-module.md §M8. All plan milestones now DONE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
/**
|
|
* Converts AI_TOOL_CATALOG entries to OpenAI-spec function schemas.
|
|
*
|
|
* Every provider we support (Google Gemini, OpenAI, OpenRouter, Groq,
|
|
* Together, Ollama 0.3+) speaks the same `{ type: "function", function:
|
|
* { name, description, parameters } }` shape for tool declarations.
|
|
* Parameters are JSON Schema. This converter is the single bridge
|
|
* between our catalog format and the wire format — changing the catalog
|
|
* shape only needs this file (plus tests) to be updated.
|
|
*
|
|
* The converter keeps tools lean: no `_rationale` meta-parameter, no
|
|
* wrapper objects, no provider-specific tweaks. Identifying calls to
|
|
* a specific policy decision (propose/auto/deny) is the executor's job,
|
|
* not the model's.
|
|
*/
|
|
|
|
import type { ToolSchema } from './schemas';
|
|
|
|
/** OpenAI-compatible JSON-Schema property type. `array` / `object` are
|
|
* emitted without a nested `items` / `properties` schema — tools that
|
|
* take structured payloads describe the expected shape inside their
|
|
* human-readable description and parse in the executor. Tightening to
|
|
* a full JSON-Schema tree would be strictly better but isn't required
|
|
* by the OpenAI / Anthropic function-calling specs. */
|
|
export interface JsonSchemaProperty {
|
|
type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
|
|
description?: string;
|
|
enum?: readonly string[];
|
|
}
|
|
|
|
/** OpenAI-compatible JSON-Schema object wrapper. */
|
|
export interface JsonSchemaObject {
|
|
type: 'object';
|
|
properties: Record<string, JsonSchemaProperty>;
|
|
required: string[];
|
|
}
|
|
|
|
/** OpenAI-compatible function declaration. */
|
|
export interface FunctionSpec {
|
|
name: string;
|
|
description: string;
|
|
parameters: JsonSchemaObject;
|
|
}
|
|
|
|
/** OpenAI-compatible tool entry (only `function` tools are supported). */
|
|
export interface ToolSpec {
|
|
type: 'function';
|
|
function: FunctionSpec;
|
|
}
|
|
|
|
/** Map a ToolSchema parameter type ("string" | "number" | "boolean") to
|
|
* a JSON Schema type. Catalog values are already narrow enough to pass
|
|
* through, but we centralise the translation here for future-proofing
|
|
* (e.g. if we ever introduce `"integer"` as a distinct type). */
|
|
function mapParamType(t: string): JsonSchemaProperty['type'] {
|
|
switch (t) {
|
|
case 'string':
|
|
case 'number':
|
|
case 'integer':
|
|
case 'boolean':
|
|
case 'array':
|
|
case 'object':
|
|
return t;
|
|
default:
|
|
// Unknown types in the catalog are a bug, but don't silently
|
|
// coerce to string — that would mask the mistake.
|
|
throw new Error(`Unsupported parameter type in tool catalog: "${t}"`);
|
|
}
|
|
}
|
|
|
|
/** Convert a single ToolSchema into an OpenAI-spec ToolSpec. */
|
|
export function toolToFunctionSchema(tool: ToolSchema): ToolSpec {
|
|
const properties: Record<string, JsonSchemaProperty> = {};
|
|
const required: string[] = [];
|
|
|
|
for (const param of tool.parameters) {
|
|
const prop: JsonSchemaProperty = {
|
|
type: mapParamType(param.type),
|
|
description: param.description,
|
|
};
|
|
if (param.enum && param.enum.length > 0) {
|
|
prop.enum = param.enum;
|
|
}
|
|
properties[param.name] = prop;
|
|
if (param.required) {
|
|
required.push(param.name);
|
|
}
|
|
}
|
|
|
|
return {
|
|
type: 'function',
|
|
function: {
|
|
name: tool.name,
|
|
description: tool.description,
|
|
parameters: {
|
|
type: 'object',
|
|
properties,
|
|
required,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Convert a full tool list to OpenAI-spec entries in one call.
|
|
* Handy for building the `tools` field on a chat-completion request. */
|
|
export function toolsToFunctionSchemas(tools: readonly ToolSchema[]): ToolSpec[] {
|
|
return tools.map(toolToFunctionSchema);
|
|
}
|