Skip to content

Commit 77a728c

Browse files
committed
fix(sdks): demo 函数契约 schema 对齐 handler 真实行为并统一驼峰键
问题: 五语言 demo 的 schema 生成器统一吐 {resource}_id 下划线万金油 schema(player_id/patch/data), 与 handler 实际驼峰契约(id/name/gold/ page/pageSize→items/total)不符, 页面 selector 按真实契约生成后发布 校验按撒谎 schema 校验, 全量 422 无法发布。 修复: python/js/java/cpp/csharp demo 改为按函数注册准确驼峰 schema (与 go demo 对齐): player.update={id,name,level,vip,gold,status, server,profile}, player.list={page,pageSize}→{items,total} 等 18 函数。 snake_case 只允许出现在数据库, wire/schema 一律驼峰。 验证: 5.5 环境 python demo 热更新重注册后, resource--player 页面 rev9 发布成功(publishedVersion=9, active=true)。
1 parent a6bee05 commit 77a728c

5 files changed

Lines changed: 326 additions & 74 deletions

File tree

sdks/cpp/examples/game_demo.cpp

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <mutex>
1818
#include <sstream>
1919
#include <string>
20+
#include <utility>
2021
#include <thread>
2122
#include <vector>
2223

@@ -43,25 +44,48 @@ static std::string json_int(const std::string& key, long long val) {
4344
return "\"" + key + "\":" + std::to_string(val);
4445
}
4546

47+
// Schemas describe the handlers' real wire contract with camelCase JSON
48+
// keys. snake_case is only allowed inside databases, never on the wire.
49+
static const char* SCHEMA_OBJ = "{\"type\":\"object\"}";
50+
static const char* SCHEMA_STR = "{\"type\":\"string\"}";
51+
static const char* SCHEMA_INT = "{\"type\":\"integer\"}";
52+
53+
static std::string player_fields_schema(bool with_required) {
54+
std::string required = with_required ? ",\"required\":[\"id\"]" : "";
55+
return "{\"type\":\"object\",\"properties\":{\"id\":" + std::string(SCHEMA_STR) +
56+
",\"name\":" + SCHEMA_STR + ",\"level\":" + SCHEMA_INT + ",\"vip\":" + SCHEMA_INT +
57+
",\"gold\":" + SCHEMA_INT + ",\"status\":" + SCHEMA_STR + ",\"server\":" + SCHEMA_STR +
58+
",\"profile\":" + SCHEMA_OBJ + "}" + required + "}";
59+
}
60+
61+
static std::pair<std::string, std::string> demo_schema_for(const std::string& id) {
62+
const std::string player_out = "{\"type\":\"object\",\"properties\":{\"player\":" + std::string(SCHEMA_OBJ) + "}}";
63+
const std::string list_out = "{\"type\":\"object\",\"properties\":{\"items\":{\"type\":\"array\",\"items\":" + std::string(SCHEMA_OBJ) + "},\"total\":" + SCHEMA_INT + "}}";
64+
const std::string pagination_in = "{\"type\":\"object\",\"properties\":{\"page\":" + std::string(SCHEMA_INT) + ",\"pageSize\":" + SCHEMA_INT + "}}";
65+
const std::string id_required_in = "{\"type\":\"object\",\"properties\":{\"id\":" + std::string(SCHEMA_STR) + "},\"required\":[\"id\"]}";
66+
if (id == "player.create") return {player_fields_schema(false), player_out};
67+
if (id == "player.get") return {id_required_in, player_out};
68+
if (id == "player.update") return {player_fields_schema(true), player_out};
69+
if (id == "player.delete") return {id_required_in, "{\"type\":\"object\",\"properties\":{\"playerId\":" + std::string(SCHEMA_STR) + "}}"};
70+
if (id == "player.list") return {pagination_in, list_out};
71+
return {};
72+
}
73+
4674
static std::string input_schema_for(const std::string& resource, const std::string& operation) {
47-
const std::string id_key = resource == "inventory" ? "playerId" : resource + "_id";
48-
if (operation == "create") {
49-
return "{\"type\":\"object\",\"properties\":{\"" + id_key + "\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}}";
50-
}
51-
if (operation == "update") {
52-
return "{\"type\":\"object\",\"properties\":{\"" + id_key + "\":{\"type\":\"string\"},\"patch\":{\"type\":\"object\"}},\"required\":[\"" + id_key + "\"]}";
53-
}
54-
if (operation == "delete") {
55-
return "{\"type\":\"object\",\"properties\":{\"" + id_key + "\":{\"type\":\"string\"}},\"required\":[\"" + id_key + "\"]}";
56-
}
57-
return "{\"type\":\"object\",\"properties\":{\"" + id_key + "\":{\"type\":\"string\"}}}";
75+
return "{\"type\":\"object\",\"properties\":{}}";
5876
}
5977

6078
static void enrich_descriptor(FunctionDescriptor& desc) {
6179
desc.tags = {desc.resource, desc.operation};
6280
desc.summary = desc.resource + " " + desc.operation;
6381
desc.description = "Demo function " + desc.id + " for " + desc.resource + " " + desc.operation + " action.";
6482
desc.operation_id = desc.id;
83+
const auto demo_schema = demo_schema_for(desc.id);
84+
if (!demo_schema.first.empty()) {
85+
desc.input_schema = demo_schema.first;
86+
desc.output_schema = demo_schema.second;
87+
return;
88+
}
6589
desc.input_schema = input_schema_for(desc.resource, desc.operation);
6690
desc.output_schema = R"({"type":"object","properties":{"status":{"type":"string"},"action":{"type":"string"}}})";
6791
}

sdks/csharp/examples/GameDemo/Program.cs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -408,19 +408,39 @@ static void EnrichDescriptor(FunctionDescriptor desc)
408408
desc.Description ??= $"Demo function {desc.Id} for {desc.Resource} {desc.Operation} action.";
409409
desc.OperationId ??= desc.Id;
410410
desc.Tags ??= new List<string> { desc.Resource ?? "", desc.Operation ?? "" };
411-
desc.InputSchema ??= InputSchemaFor(desc.Resource ?? "payload", desc.Operation ?? "execute");
412-
desc.OutputSchema ??= "{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\"},\"action\":{\"type\":\"string\"}}}";
411+
var schemas = SchemasFor(desc.Id ?? "");
412+
desc.InputSchema ??= schemas.Input;
413+
desc.OutputSchema ??= schemas.Output;
413414
}
414415

415-
static string InputSchemaFor(string resource, string operation)
416+
// Schemas describe the handlers' real wire contract with camelCase JSON
417+
// keys. snake_case is only allowed inside databases, never on the wire.
418+
static readonly string SchemaObj = "{\"type\":\"object\"}";
419+
static readonly string SchemaStr = "{\"type\":\"string\"}";
420+
static readonly string SchemaInt = "{\"type\":\"integer\"}";
421+
static readonly string PlayerFields =
422+
"{\"id\":" + SchemaStr + ",\"name\":" + SchemaStr + ",\"level\":" + SchemaInt + ",\"vip\":" + SchemaInt +
423+
",\"gold\":" + SchemaInt + ",\"status\":" + SchemaStr + ",\"server\":" + SchemaStr + ",\"profile\":" + SchemaObj + "}";
424+
425+
static (string Input, string Output) SchemasFor(string id) => id switch
426+
{
427+
"player.create" => (BuildObj("{" + PlayerFields + "}"), BuildObj("{\"player\":" + SchemaObj + "}")),
428+
"player.get" => (BuildObj("{\"id\":" + SchemaStr + "}", new[] { "id" }), BuildObj("{\"player\":" + SchemaObj + "}")),
429+
"player.update" => (BuildObj("{" + PlayerFields + "}", new[] { "id" }), BuildObj("{\"player\":" + SchemaObj + "}")),
430+
"player.delete" => (BuildObj("{\"id\":" + SchemaStr + "}", new[] { "id" }), BuildObj("{\"playerId\":" + SchemaStr + "}")),
431+
"player.list" => (BuildObj("{\"page\":" + SchemaInt + ",\"pageSize\":" + SchemaInt + "}"),
432+
BuildObj("{\"items\":{\"type\":\"array\",\"items\":" + SchemaObj + "},\"total\":" + SchemaInt + "}")),
433+
_ => (BuildObj("{}"), "{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\"},\"action\":{\"type\":\"string\"}}}"),
434+
};
435+
436+
static string BuildObj(string props, string[]? required = null)
416437
{
417-
var idKey = resource == "inventory" ? "playerId" : $"{resource}_id";
418-
return operation switch
438+
var schema = "{\"type\":\"object\",\"properties\":" + props + "}";
439+
if (required != null && required.Length > 0)
419440
{
420-
"create" => $"{{\"type\":\"object\",\"properties\":{{\"{idKey}\":{{\"type\":\"string\"}},\"data\":{{\"type\":\"object\"}}}}}}",
421-
"update" => $"{{\"type\":\"object\",\"properties\":{{\"{idKey}\":{{\"type\":\"string\"}},\"patch\":{{\"type\":\"object\"}}}},\"required\":[\"{idKey}\"]}}",
422-
"delete" => $"{{\"type\":\"object\",\"properties\":{{\"{idKey}\":{{\"type\":\"string\"}}}},\"required\":[\"{idKey}\"]}}",
423-
_ => $"{{\"type\":\"object\",\"properties\":{{\"{idKey}\":{{\"type\":\"string\"}}}}}}",
424-
};
441+
schema = schema.Substring(0, schema.Length - 1) + ",\"required\":[\"" + string.Join("\",\"", required) + "\"]}";
442+
}
443+
return schema;
425444
}
445+
426446
}

sdks/java/examples/game-demo/src/main/java/com/croupier/sdk/examples/GameDemo.java

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -522,31 +522,80 @@ private static void enrichDescriptor(FunctionDescriptor desc) {
522522
desc.getId(), desc.getResource(), desc.getOperation()
523523
));
524524
desc.setOperationId(desc.getId());
525-
desc.setInputSchema(inputSchemaFor(desc.getResource(), desc.getOperation()));
525+
String[] schemas = SCHEMAS.get(desc.getId());
526+
if (schemas != null) {
527+
desc.setInputSchema(schemas[0]);
528+
desc.setOutputSchema(schemas[1]);
529+
return;
530+
}
531+
desc.setInputSchema("{\"type\":\"object\",\"properties\":{}}");
526532
desc.setOutputSchema("{\"type\":\"object\",\"properties\":{\"status\":{\"type\":\"string\"},\"action\":{\"type\":\"string\"}}}");
527533
}
528534

529-
private static String inputSchemaFor(String resource, String operation) {
530-
String idKey = "inventory".equals(resource) ? "playerId" : resource + "_id";
531-
return switch (operation) {
532-
case "create" -> String.format(
533-
"{\"type\":\"object\",\"properties\":{\"%s\":{\"type\":\"string\"},\"data\":{\"type\":\"object\"}}}",
534-
idKey
535-
);
536-
case "update" -> String.format(
537-
"{\"type\":\"object\",\"properties\":{\"%s\":{\"type\":\"string\"},\"patch\":{\"type\":\"object\"}},\"required\":[\"%s\"]}",
538-
idKey, idKey
539-
);
540-
case "delete" -> String.format(
541-
"{\"type\":\"object\",\"properties\":{\"%s\":{\"type\":\"string\"}},\"required\":[\"%s\"]}",
542-
idKey, idKey
543-
);
544-
default -> String.format(
545-
"{\"type\":\"object\",\"properties\":{\"%s\":{\"type\":\"string\"}}}",
546-
idKey
547-
);
548-
};
549-
}
535+
// Schemas describe the handlers' real wire contract with camelCase JSON
536+
// keys. snake_case is only allowed inside databases, never on the wire.
537+
private static final String OBJ = "{\"type\":\"object\"}";
538+
private static final String STR = "{\"type\":\"string\"}";
539+
private static final String INT = "{\"type\":\"integer\"}";
540+
private static final String PLAYER_FIELDS = "{\"id\":\" + STR + \",\"name\":\" + STR + \",\"level\":\" + INT + \",\"vip\":\" + INT + \",\"gold\":\" + INT + \",\"status\":\" + STR + \",\"server\":\" + STR + \",\"profile\":\"\" + OBJ + "\"}";
541+
542+
private static final Map<String, String[]> SCHEMAS = Map.ofEntries(
543+
Map.entry("player.create", new String[]{
544+
"{\"type\":\"object\",\"properties\":" + PLAYER_FIELDS + "}",
545+
"{\"type\":\"object\",\"properties\":{\"player\":" + OBJ + "}}"}),
546+
Map.entry("player.get", new String[]{
547+
"{\"type\":\"object\",\"properties\":{\"id\":" + STR + "},\"required\":[\"id\"]}",
548+
"{\"type\":\"object\",\"properties\":{\"player\":" + OBJ + "}}"}),
549+
Map.entry("player.update", new String[]{
550+
"{\"type\":\"object\",\"properties\":" + PLAYER_FIELDS + ",\"required\":[\"id\"]}",
551+
"{\"type\":\"object\",\"properties\":{\"player\":" + OBJ + "}}"}),
552+
Map.entry("player.delete", new String[]{
553+
"{\"type\":\"object\",\"properties\":{\"id\":" + STR + "},\"required\":[\"id\"]}",
554+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + "}}"}),
555+
Map.entry("player.list", new String[]{
556+
"{\"type\":\"object\",\"properties\":{\"page\":" + INT + ",\"pageSize\":" + INT + "}}",
557+
"{\"type\":\"object\",\"properties\":{\"items\":{\"type\":\"array\",\"items\":" + OBJ + "},\"total\":" + INT + "}}"}),
558+
Map.entry("order.create", new String[]{
559+
"{\"type\":\"object\",\"properties\":{\"id\":" + STR + ",\"playerId\":" + STR + ",\"productId\":" + STR + ",\"amount\":" + INT + ",\"currency\":" + STR + ",\"status\":" + STR + ",\"channel\":" + STR + ",\"attributes\":" + OBJ + "}}",
560+
"{\"type\":\"object\",\"properties\":{\"order\":" + OBJ + "}}"}),
561+
Map.entry("order.get", new String[]{
562+
"{\"type\":\"object\",\"properties\":{\"id\":" + STR + "},\"required\":[\"id\"]}",
563+
"{\"type\":\"object\",\"properties\":{\"order\":" + OBJ + "}}"}),
564+
Map.entry("order.update", new String[]{
565+
"{\"type\":\"object\",\"properties\":{\"id\":" + STR + ",\"status\":" + STR + ",\"channel\":" + STR + ",\"amount\":" + INT + ",\"attributes\":" + OBJ + "},\"required\":[\"id\"]}",
566+
"{\"type\":\"object\",\"properties\":{\"order\":" + OBJ + "}}"}),
567+
Map.entry("order.delete", new String[]{
568+
"{\"type\":\"object\",\"properties\":{\"id\":" + STR + "},\"required\":[\"id\"]}",
569+
"{\"type\":\"object\",\"properties\":{\"orderId\":" + STR + "}}"}),
570+
Map.entry("order.list", new String[]{
571+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + ",\"page\":" + INT + ",\"pageSize\":" + INT + "}}",
572+
"{\"type\":\"object\",\"properties\":{\"items\":{\"type\":\"array\",\"items\":" + OBJ + "},\"total\":" + INT + "}}"}),
573+
Map.entry("leaderboard.list", new String[]{
574+
"{\"type\":\"object\",\"properties\":{\"page\":" + INT + ",\"pageSize\":" + INT + "}}",
575+
"{\"type\":\"object\",\"properties\":{\"items\":{\"type\":\"array\",\"items\":" + OBJ + "},\"total\":" + INT + "}}"}),
576+
Map.entry("leaderboard.upsert", new String[]{
577+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + ",\"score\":" + INT + "},\"required\":[\"playerId\"]}",
578+
"{\"type\":\"object\",\"properties\":{\"entry\":" + OBJ + "}}"}),
579+
Map.entry("leaderboard.reset", new String[]{"{\"type\":\"object\",\"properties\":{}}", "{\"type\":\"object\",\"properties\":{}}"}),
580+
Map.entry("inventory.list", new String[]{
581+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + "},\"required\":[\"playerId\"]}",
582+
"{\"type\":\"object\",\"properties\":{\"items\":{\"type\":\"array\",\"items\":" + OBJ + "}}}"}),
583+
Map.entry("inventory.grant", new String[]{
584+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + ",\"templateId\":" + STR + ",\"quantity\":" + INT + "},\"required\":[\"playerId\",\"templateId\"]}",
585+
"{\"type\":\"object\",\"properties\":{\"item\":" + OBJ + "}}"}),
586+
Map.entry("inventory.consume", new String[]{
587+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + ",\"templateId\":" + STR + ",\"quantity\":" + INT + "},\"required\":[\"playerId\",\"templateId\"]}",
588+
"{\"type\":\"object\",\"properties\":{\"item\":" + OBJ + "}}"}),
589+
Map.entry("mail.send", new String[]{
590+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + ",\"title\":" + STR + ",\"content\":" + STR + ",\"reward\":" + OBJ + ",\"expireAt\":" + STR + "},\"required\":[\"playerId\"]}",
591+
"{\"type\":\"object\",\"properties\":{\"mail\":" + OBJ + "}}"}),
592+
Map.entry("mail.list", new String[]{
593+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + "},\"required\":[\"playerId\"]}",
594+
"{\"type\":\"object\",\"properties\":{\"items\":{\"type\":\"array\",\"items\":" + OBJ + "},\"total\":" + INT + "}}"}),
595+
Map.entry("mail.claim", new String[]{
596+
"{\"type\":\"object\",\"properties\":{\"playerId\":" + STR + ",\"mailId\":" + STR + "},\"required\":[\"playerId\",\"mailId\"]}",
597+
"{\"type\":\"object\",\"properties\":{\"mail\":" + OBJ + "}}"})
598+
);
550599

551600
// ==================== Main ====================
552601

sdks/js/examples/game_demo.ts

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -383,40 +383,90 @@ function enrichDescriptor(desc: FunctionDescriptor): FunctionDescriptor {
383383
desc.description ||
384384
`Demo function ${desc.id} for ${desc.resource || "unscoped"} ${desc.operation || "invoke"} operations.`,
385385
operation_id: desc.operation_id || desc.id,
386-
input_schema: desc.input_schema || inputSchemaFor(desc.resource || "object", desc.operation || "invoke"),
387-
output_schema: desc.output_schema || {
388-
type: "object",
389-
properties: {
390-
status: { type: "string" },
391-
action: { type: "string" },
392-
},
393-
},
386+
input_schema: desc.input_schema || schemasFor(desc.id).input,
387+
output_schema: desc.output_schema || schemasFor(desc.id).output,
394388
};
395389
}
396390

397-
function inputSchemaFor(resource: string, operation: string): Record<string, unknown> {
398-
const idKey = resource === "inventory" ? "playerId" : `${resource}_id`;
399-
if (operation === "create") {
400-
return {
401-
type: "object",
402-
properties: { [idKey]: { type: "string" }, data: { type: "object" } },
403-
};
404-
}
405-
if (operation === "update") {
406-
return {
407-
type: "object",
408-
properties: { [idKey]: { type: "string" }, patch: { type: "object" } },
409-
required: [idKey],
410-
};
411-
}
412-
if (operation === "delete") {
413-
return {
414-
type: "object",
415-
properties: { [idKey]: { type: "string" } },
416-
required: [idKey],
417-
};
418-
}
419-
return { type: "object", properties: { [idKey]: { type: "string" } } };
391+
// Schemas describe the handlers' real wire contract with camelCase JSON
392+
// keys. snake_case is only allowed inside databases, never on the wire.
393+
const s = (): { type: string } => ({ type: "string" });
394+
const i = (): { type: string } => ({ type: "integer" });
395+
396+
const PLAYER_FIELDS: Record<string, unknown> = {
397+
id: s(), name: s(), level: i(), vip: i(),
398+
gold: i(), status: s(), server: s(), profile: { type: "object" },
399+
};
400+
const ORDER_FIELDS: Record<string, unknown> = {
401+
id: s(), playerId: s(), productId: s(), amount: i(),
402+
currency: s(), status: s(), channel: s(), attributes: { type: "object" },
403+
};
404+
const PAGINATION: Record<string, unknown> = { page: i(), pageSize: i() };
405+
const LIST_OUTPUT: Record<string, unknown> = {
406+
type: "object",
407+
properties: { items: { type: "array", items: { type: "object" } }, total: i() },
408+
};
409+
410+
const SCHEMAS: Record<string, { input: Record<string, unknown>; output: Record<string, unknown> }> = {
411+
"player.create": { input: obj({ ...PLAYER_FIELDS }), output: obj({ player: { type: "object" } }) },
412+
"player.get": { input: obj({ id: s() }, ["id"]), output: obj({ player: { type: "object" } }) },
413+
"player.update": { input: obj({ ...PLAYER_FIELDS }, ["id"]), output: obj({ player: { type: "object" } }) },
414+
"player.delete": { input: obj({ id: s() }, ["id"]), output: obj({ playerId: s() }) },
415+
"player.list": { input: obj({ ...PAGINATION }), output: LIST_OUTPUT },
416+
"order.create": { input: obj({ ...ORDER_FIELDS }), output: obj({ order: { type: "object" } }) },
417+
"order.get": { input: obj({ id: s() }, ["id"]), output: obj({ order: { type: "object" } }) },
418+
"order.update": {
419+
input: obj(pick(ORDER_FIELDS, ["id", "status", "channel", "amount", "attributes"]), ["id"]),
420+
output: obj({ order: { type: "object" } }),
421+
},
422+
"order.delete": { input: obj({ id: s() }, ["id"]), output: obj({ orderId: s() }) },
423+
"order.list": { input: obj({ playerId: s(), ...PAGINATION }), output: LIST_OUTPUT },
424+
"leaderboard.list": { input: obj({ ...PAGINATION }), output: LIST_OUTPUT },
425+
"leaderboard.upsert": {
426+
input: obj({ playerId: s(), score: i() }, ["playerId"]),
427+
output: obj({ entry: { type: "object" } }),
428+
},
429+
"leaderboard.reset": { input: obj({}), output: obj({}) },
430+
"inventory.list": {
431+
input: obj({ playerId: s() }, ["playerId"]),
432+
output: obj({ items: { type: "array", items: { type: "object" } } }),
433+
},
434+
"inventory.grant": {
435+
input: obj({ playerId: s(), templateId: s(), quantity: i() }, ["playerId", "templateId"]),
436+
output: obj({ item: { type: "object" } }),
437+
},
438+
"inventory.consume": {
439+
input: obj({ playerId: s(), templateId: s(), quantity: i() }, ["playerId", "templateId"]),
440+
output: obj({ item: { type: "object" } }),
441+
},
442+
"mail.send": {
443+
input: obj({ playerId: s(), title: s(), content: s(), reward: { type: "object" }, expireAt: s() }, ["playerId"]),
444+
output: obj({ mail: { type: "object" } }),
445+
},
446+
"mail.list": { input: obj({ playerId: s() }, ["playerId"]), output: LIST_OUTPUT },
447+
"mail.claim": {
448+
input: obj({ playerId: s(), mailId: s() }, ["playerId", "mailId"]),
449+
output: obj({ mail: { type: "object" } }),
450+
},
451+
};
452+
453+
function obj(props: Record<string, unknown>, required?: string[]): Record<string, unknown> {
454+
const schema: Record<string, unknown> = { type: "object", properties: props };
455+
if (required && required.length > 0) schema.required = required;
456+
return schema;
457+
}
458+
459+
function pick(source: Record<string, unknown>, keys: string[]): Record<string, unknown> {
460+
const out: Record<string, unknown> = {};
461+
for (const key of keys) if (key in source) out[key] = source[key];
462+
return out;
463+
}
464+
465+
function schemasFor(functionId: string): { input: Record<string, unknown>; output: Record<string, unknown> } {
466+
return SCHEMAS[functionId] || {
467+
input: { type: "object", properties: {} },
468+
output: { type: "object", properties: { status: s(), action: s() } },
469+
};
420470
}
421471

422472
// ==================== Main ====================

0 commit comments

Comments
 (0)