handler.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. "use strict";
  2. const { parseCotBlocks } = require("./cot-parser");
  3. const DEFAULT_BASE_URL = process.env.TASK_BOARD_API_BASE || "http://127.0.0.1:3001";
  4. const DEFAULT_TOKEN = process.env.TASK_BOARD_ADMIN_TOKEN || "dev-task-board-token";
  5. function withAuthHeaders(token) {
  6. return {
  7. "Content-Type": "application/json",
  8. Authorization: `Bearer ${token}`,
  9. };
  10. }
  11. async function requestJson(baseUrl, token, path, method = "GET", payload) {
  12. const response = await fetch(`${baseUrl.replace(/\/+$/, "")}${path}`, {
  13. method,
  14. headers: withAuthHeaders(token),
  15. body: payload === undefined ? undefined : JSON.stringify(payload),
  16. });
  17. const data = await response.json().catch(() => ({}));
  18. if (!response.ok) {
  19. throw new Error(data.error || `${method} ${path} failed: ${response.status}`);
  20. }
  21. return data;
  22. }
  23. async function upsertNode(node, options = {}) {
  24. const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
  25. const token = options.token || DEFAULT_TOKEN;
  26. return requestJson(baseUrl, token, "/api/admin/nodes", "POST", node);
  27. }
  28. async function upsertEdge(edge, options = {}) {
  29. const baseUrl = options.baseUrl || DEFAULT_BASE_URL;
  30. const token = options.token || DEFAULT_TOKEN;
  31. return requestJson(baseUrl, token, "/api/admin/edges", "POST", edge);
  32. }
  33. function buildTurnNode(params) {
  34. const content = String(params.content || "").trim();
  35. const ts = params.timestamp || new Date().toISOString();
  36. const messageId = params.messageId ? String(params.messageId) : "";
  37. return {
  38. id: messageId ? `turn-${messageId}` : undefined,
  39. conversationId: params.conversationId,
  40. parentId: params.parentId || null,
  41. nodeType: "thought",
  42. name: content.slice(0, 42) || "Turn",
  43. status: "completed",
  44. content,
  45. timestamps: {
  46. created: ts,
  47. started: ts,
  48. completed: ts,
  49. },
  50. llmDiagnostics: params.llmDiagnostics || {},
  51. notes: params.notes || "",
  52. };
  53. }
  54. async function ingestMessage(params, options = {}) {
  55. const turnNode = buildTurnNode(params);
  56. const createdTurn = await upsertNode(turnNode, options);
  57. if (params.parentId) {
  58. await upsertEdge(
  59. {
  60. from: params.parentId,
  61. to: createdTurn.id,
  62. type: "hierarchy",
  63. },
  64. options,
  65. );
  66. }
  67. if (!params.parseCot) {
  68. return { turnNode: createdTurn, cotNodes: [] };
  69. }
  70. const cot = parseCotBlocks(params.content || "");
  71. const cotNodes = [];
  72. let previousId = createdTurn.id;
  73. for (const block of cot) {
  74. const cotNode = await upsertNode(
  75. {
  76. conversationId: params.conversationId,
  77. parentId: previousId,
  78. nodeType: block.nodeType,
  79. name: block.name,
  80. content: block.content,
  81. status: "completed",
  82. timestamps: {
  83. created: params.timestamp || new Date().toISOString(),
  84. },
  85. },
  86. options,
  87. );
  88. await upsertEdge(
  89. {
  90. from: previousId,
  91. to: cotNode.id,
  92. type: "hierarchy",
  93. },
  94. options,
  95. );
  96. previousId = cotNode.id;
  97. cotNodes.push(cotNode);
  98. }
  99. return { turnNode: createdTurn, cotNodes };
  100. }
  101. module.exports = {
  102. ingestMessage,
  103. upsertNode,
  104. upsertEdge,
  105. };