@avocadostudio-ai/orchestrator-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/LICENSE +201 -0
  2. package/dist/agent/agent-context.d.ts +19 -0
  3. package/dist/agent/agent-context.js +67 -0
  4. package/dist/agent/agent-logger.d.ts +5 -0
  5. package/dist/agent/agent-logger.js +22 -0
  6. package/dist/agent/agent-loop-openai.d.ts +8 -0
  7. package/dist/agent/agent-loop-openai.js +172 -0
  8. package/dist/agent/agent-loop.d.ts +56 -0
  9. package/dist/agent/agent-loop.js +167 -0
  10. package/dist/agent/agent-provider.d.ts +28 -0
  11. package/dist/agent/agent-provider.js +63 -0
  12. package/dist/agent/agent-tools.d.ts +28 -0
  13. package/dist/agent/agent-tools.js +899 -0
  14. package/dist/agent/context/editing-guidelines.md +46 -0
  15. package/dist/agent/context/role.md +39 -0
  16. package/dist/agent/integration-prompt.d.ts +9 -0
  17. package/dist/agent/integration-prompt.js +154 -0
  18. package/dist/agent/sites-agent-context.d.ts +12 -0
  19. package/dist/agent/sites-agent-context.js +316 -0
  20. package/dist/agent/sites-agent-shared.d.ts +161 -0
  21. package/dist/agent/sites-agent-shared.js +1101 -0
  22. package/dist/agent/sites-agent-tools.d.ts +18 -0
  23. package/dist/agent/sites-agent-tools.js +1227 -0
  24. package/dist/chat/anthropic-cache.d.ts +20 -0
  25. package/dist/chat/anthropic-cache.js +54 -0
  26. package/dist/chat/anthropic-planner.d.ts +98 -0
  27. package/dist/chat/anthropic-planner.js +1012 -0
  28. package/dist/chat/changelog-coverage-validator.d.ts +37 -0
  29. package/dist/chat/changelog-coverage-validator.js +215 -0
  30. package/dist/chat/chat-pipeline-context.d.ts +211 -0
  31. package/dist/chat/chat-pipeline-context.js +249 -0
  32. package/dist/chat/chat-pipeline-deterministic.d.ts +61 -0
  33. package/dist/chat/chat-pipeline-deterministic.js +407 -0
  34. package/dist/chat/chat-pipeline-image.d.ts +86 -0
  35. package/dist/chat/chat-pipeline-image.js +897 -0
  36. package/dist/chat/chat-pipeline-shared.d.ts +69 -0
  37. package/dist/chat/chat-pipeline-shared.js +212 -0
  38. package/dist/chat/chat-pipeline-translation.d.ts +27 -0
  39. package/dist/chat/chat-pipeline-translation.js +417 -0
  40. package/dist/chat/chat-pipeline-ui.d.ts +14 -0
  41. package/dist/chat/chat-pipeline-ui.js +244 -0
  42. package/dist/chat/chat-pipeline.d.ts +99 -0
  43. package/dist/chat/chat-pipeline.js +3999 -0
  44. package/dist/chat/decomposer.d.ts +21 -0
  45. package/dist/chat/decomposer.js +65 -0
  46. package/dist/chat/gemini-planner.d.ts +70 -0
  47. package/dist/chat/gemini-planner.js +541 -0
  48. package/dist/chat/hallucination-validator.d.ts +36 -0
  49. package/dist/chat/hallucination-validator.js +110 -0
  50. package/dist/chat/locale-strings.d.ts +47 -0
  51. package/dist/chat/locale-strings.js +100 -0
  52. package/dist/chat/plan-json-schema.d.ts +133 -0
  53. package/dist/chat/plan-json-schema.js +112 -0
  54. package/dist/chat/planner-types.d.ts +120 -0
  55. package/dist/chat/planner-types.js +66 -0
  56. package/dist/chat/planner.d.ts +148 -0
  57. package/dist/chat/planner.js +1361 -0
  58. package/dist/chat/prompts.d.ts +67 -0
  59. package/dist/chat/prompts.js +356 -0
  60. package/dist/chat/provider-routing.d.ts +14 -0
  61. package/dist/chat/provider-routing.js +27 -0
  62. package/dist/chat/variation-pipeline.d.ts +135 -0
  63. package/dist/chat/variation-pipeline.js +837 -0
  64. package/dist/chat/vision-alt-generator.d.ts +35 -0
  65. package/dist/chat/vision-alt-generator.js +152 -0
  66. package/dist/cms/adapter.d.ts +62 -0
  67. package/dist/cms/adapter.js +1 -0
  68. package/dist/cms/bootstrap.d.ts +17 -0
  69. package/dist/cms/bootstrap.js +85 -0
  70. package/dist/cms/editor-api-adapter.d.ts +23 -0
  71. package/dist/cms/editor-api-adapter.js +71 -0
  72. package/dist/cms/index.d.ts +4 -0
  73. package/dist/cms/index.js +3 -0
  74. package/dist/cms/json-file-adapter.d.ts +11 -0
  75. package/dist/cms/json-file-adapter.js +62 -0
  76. package/dist/demo-mode.d.ts +59 -0
  77. package/dist/demo-mode.js +201 -0
  78. package/dist/errors.d.ts +67 -0
  79. package/dist/errors.js +129 -0
  80. package/dist/http/chat-stream-resumable.d.ts +108 -0
  81. package/dist/http/chat-stream-resumable.js +290 -0
  82. package/dist/http/chat-stream.d.ts +99 -0
  83. package/dist/http/chat-stream.js +92 -0
  84. package/dist/image/gdrive-client.d.ts +22 -0
  85. package/dist/image/gdrive-client.js +215 -0
  86. package/dist/image/image-helpers.d.ts +95 -0
  87. package/dist/image/image-helpers.js +488 -0
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +1 -0
  90. package/dist/jira/jira-approval.d.ts +22 -0
  91. package/dist/jira/jira-approval.js +51 -0
  92. package/dist/jira/jira-client.d.ts +44 -0
  93. package/dist/jira/jira-client.js +313 -0
  94. package/dist/jira/jira-poller.d.ts +46 -0
  95. package/dist/jira/jira-poller.js +184 -0
  96. package/dist/jira/jira-processor.d.ts +103 -0
  97. package/dist/jira/jira-processor.js +1085 -0
  98. package/dist/jira/jira-types.d.ts +117 -0
  99. package/dist/jira/jira-types.js +38 -0
  100. package/dist/logger.d.ts +12 -0
  101. package/dist/logger.js +28 -0
  102. package/dist/migration/mcp-server-stdio.d.ts +8 -0
  103. package/dist/migration/mcp-server-stdio.js +672 -0
  104. package/dist/migration/migration-prompt.d.ts +7 -0
  105. package/dist/migration/migration-prompt.js +197 -0
  106. package/dist/migration/migration-tools.d.ts +17 -0
  107. package/dist/migration/migration-tools.js +159 -0
  108. package/dist/migration/scrape-cache.d.ts +9 -0
  109. package/dist/migration/scrape-cache.js +19 -0
  110. package/dist/nlp/deterministic-planner-context.d.ts +141 -0
  111. package/dist/nlp/deterministic-planner-context.js +362 -0
  112. package/dist/nlp/deterministic-planner-pages.d.ts +26 -0
  113. package/dist/nlp/deterministic-planner-pages.js +170 -0
  114. package/dist/nlp/deterministic-planner-patches.d.ts +80 -0
  115. package/dist/nlp/deterministic-planner-patches.js +508 -0
  116. package/dist/nlp/deterministic-planner-refs.d.ts +33 -0
  117. package/dist/nlp/deterministic-planner-refs.js +164 -0
  118. package/dist/nlp/deterministic-planner-suggestions.d.ts +49 -0
  119. package/dist/nlp/deterministic-planner-suggestions.js +579 -0
  120. package/dist/nlp/deterministic-planner.d.ts +85 -0
  121. package/dist/nlp/deterministic-planner.js +1631 -0
  122. package/dist/nlp/intent-detection.d.ts +309 -0
  123. package/dist/nlp/intent-detection.js +730 -0
  124. package/dist/nlp/intent-helpers.d.ts +15 -0
  125. package/dist/nlp/intent-helpers.js +243 -0
  126. package/dist/nlp/intent-patterns.d.ts +40 -0
  127. package/dist/nlp/intent-patterns.js +223 -0
  128. package/dist/nlp/plan-normalizer.d.ts +41 -0
  129. package/dist/nlp/plan-normalizer.js +1537 -0
  130. package/dist/ops/destructive-action-gate.d.ts +44 -0
  131. package/dist/ops/destructive-action-gate.js +90 -0
  132. package/dist/ops/ops-engine.d.ts +151 -0
  133. package/dist/ops/ops-engine.js +1394 -0
  134. package/dist/publish/diff-engine.d.ts +18 -0
  135. package/dist/publish/diff-engine.js +305 -0
  136. package/dist/publish/publish-helpers.d.ts +87 -0
  137. package/dist/publish/publish-helpers.js +521 -0
  138. package/dist/publish/publish-target-registry.d.ts +7 -0
  139. package/dist/publish/publish-target-registry.js +61 -0
  140. package/dist/publish/publish-target.d.ts +81 -0
  141. package/dist/publish/publish-target.js +1 -0
  142. package/dist/publish/targets/deploy-hook.d.ts +13 -0
  143. package/dist/publish/targets/deploy-hook.js +123 -0
  144. package/dist/publish/targets/git.d.ts +13 -0
  145. package/dist/publish/targets/git.js +55 -0
  146. package/dist/publish/targets/site-contract.d.ts +19 -0
  147. package/dist/publish/targets/site-contract.js +124 -0
  148. package/dist/state/content-source.d.ts +17 -0
  149. package/dist/state/content-source.js +1 -0
  150. package/dist/state/in-memory-content-source.d.ts +27 -0
  151. package/dist/state/in-memory-content-source.js +51 -0
  152. package/dist/state/session-lock.d.ts +13 -0
  153. package/dist/state/session-lock.js +29 -0
  154. package/dist/state/session-state.d.ts +310 -0
  155. package/dist/state/session-state.js +1083 -0
  156. package/dist/state/sqlite-store-singleton.d.ts +31 -0
  157. package/dist/state/sqlite-store-singleton.js +170 -0
  158. package/dist/state/sqlite-store.d.ts +135 -0
  159. package/dist/state/sqlite-store.js +421 -0
  160. package/dist/telemetry/chat-telemetry.d.ts +105 -0
  161. package/dist/telemetry/chat-telemetry.js +247 -0
  162. package/dist/telemetry/eval-candidate-store.d.ts +50 -0
  163. package/dist/telemetry/eval-candidate-store.js +120 -0
  164. package/dist/telemetry/feedback-store.d.ts +34 -0
  165. package/dist/telemetry/feedback-store.js +76 -0
  166. package/dist/telemetry/jira-telemetry.d.ts +57 -0
  167. package/dist/telemetry/jira-telemetry.js +68 -0
  168. package/dist/telemetry/migration-telemetry.d.ts +35 -0
  169. package/dist/telemetry/migration-telemetry.js +40 -0
  170. package/dist/telemetry/usage.d.ts +24 -0
  171. package/dist/telemetry/usage.js +80 -0
  172. package/dist/tools/builtin-registrations.d.ts +12 -0
  173. package/dist/tools/builtin-registrations.js +33 -0
  174. package/dist/tools/builtins/gdrive-browse.d.ts +3 -0
  175. package/dist/tools/builtins/gdrive-browse.js +68 -0
  176. package/dist/tools/builtins/image-generate.d.ts +3 -0
  177. package/dist/tools/builtins/image-generate.js +211 -0
  178. package/dist/tools/builtins/unsplash-get-by-id.d.ts +23 -0
  179. package/dist/tools/builtins/unsplash-get-by-id.js +119 -0
  180. package/dist/tools/builtins/unsplash-search.d.ts +3 -0
  181. package/dist/tools/builtins/unsplash-search.js +74 -0
  182. package/dist/tools/executor.d.ts +23 -0
  183. package/dist/tools/executor.js +169 -0
  184. package/dist/tools/index.d.ts +5 -0
  185. package/dist/tools/index.js +5 -0
  186. package/dist/tools/registry.d.ts +21 -0
  187. package/dist/tools/registry.js +75 -0
  188. package/dist/tools/runtime.d.ts +27 -0
  189. package/dist/tools/runtime.js +48 -0
  190. package/dist/tools/schema-validator.d.ts +24 -0
  191. package/dist/tools/schema-validator.js +88 -0
  192. package/dist/tools/types.d.ts +86 -0
  193. package/dist/tools/types.js +1 -0
  194. package/dist/variation-images.d.ts +19 -0
  195. package/dist/variation-images.js +12 -0
  196. package/package.json +78 -0
@@ -0,0 +1,74 @@
1
+ import { imageKeywordsFromQuery, resolveUnsplashImage } from "../../image/image-helpers.js";
2
+ export const unsplashSearchManifest = {
3
+ name: "unsplash.search",
4
+ description: "Search Unsplash for candidate photos relevant to a query",
5
+ capability: "read",
6
+ timeoutMs: 6000,
7
+ retryPolicy: { maxAttempts: 2, backoffMs: 150 },
8
+ idempotent: true,
9
+ inputSchema: {
10
+ type: "object",
11
+ additionalProperties: false,
12
+ required: ["query"],
13
+ properties: {
14
+ query: { type: "string", description: "Natural-language photo search query" },
15
+ limit: { type: "integer", description: "Maximum number of candidates to return (1-5)" }
16
+ }
17
+ },
18
+ outputSchema: {
19
+ type: "object",
20
+ additionalProperties: false,
21
+ required: ["items"],
22
+ properties: {
23
+ items: {
24
+ type: "array",
25
+ items: {
26
+ type: "object",
27
+ additionalProperties: false,
28
+ required: ["id", "imageUrl", "thumbUrl", "alt", "author", "sourceUrl"],
29
+ properties: {
30
+ id: { type: "string" },
31
+ imageUrl: { type: "string" },
32
+ thumbUrl: { type: "string" },
33
+ alt: { type: "string" },
34
+ author: { type: "string" },
35
+ sourceUrl: { type: "string" }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ };
42
+ function mapResultToCandidate(url, alt) {
43
+ const sanitizedAlt = alt.trim() || "Unsplash image";
44
+ const id = url.replace(/[^a-zA-Z0-9]/g, "").slice(-18) || `img_${Date.now()}`;
45
+ const thumbUrl = url.includes("?") ? `${url}&w=480&q=70` : `${url}?w=480&q=70`;
46
+ const sourceUrl = url.includes("unsplash.com") ? url : `https://unsplash.com/s/photos/${encodeURIComponent(sanitizedAlt)}`;
47
+ return {
48
+ id,
49
+ imageUrl: url,
50
+ thumbUrl,
51
+ alt: sanitizedAlt,
52
+ author: "Unsplash",
53
+ sourceUrl
54
+ };
55
+ }
56
+ export const unsplashSearchHandler = async ({ input }) => {
57
+ const typed = (input ?? {});
58
+ const query = typeof typed.query === "string" ? typed.query.trim() : "";
59
+ const limitRaw = typeof typed.limit === "number" ? typed.limit : 3;
60
+ const limit = Math.min(5, Math.max(1, Math.trunc(limitRaw)));
61
+ const usedImageUrls = new Set();
62
+ if (!query)
63
+ return { items: [] };
64
+ const items = [];
65
+ const keywords = imageKeywordsFromQuery(query, 4);
66
+ for (let idx = 0; idx < limit; idx += 1) {
67
+ const resolved = await resolveUnsplashImage(query, { variationIndex: idx, usedImageUrls, subjectKeywords: keywords }, undefined);
68
+ if (!resolved)
69
+ continue;
70
+ usedImageUrls.add(resolved.url);
71
+ items.push(mapResultToCandidate(resolved.url, resolved.alt));
72
+ }
73
+ return { items };
74
+ };
@@ -0,0 +1,23 @@
1
+ import type { ToolCallContext, ToolExecutionEvent, ToolExecutionPolicy, ToolResult } from "./types.ts";
2
+ import { ToolRegistry } from "./registry.ts";
3
+ type Logger = {
4
+ info: (payload: Record<string, unknown>, message?: string) => void;
5
+ warn: (payload: Record<string, unknown>, message?: string) => void;
6
+ };
7
+ type ExecuteArgs = {
8
+ toolName: string;
9
+ input: unknown;
10
+ context: ToolCallContext;
11
+ policy?: Partial<ToolExecutionPolicy>;
12
+ signal?: AbortSignal;
13
+ };
14
+ export declare class ToolExecutor {
15
+ private readonly registry;
16
+ private readonly logger;
17
+ private readonly onExecution?;
18
+ private readonly idempotencyCache;
19
+ constructor(registry: ToolRegistry, logger: Logger, onExecution?: ((event: ToolExecutionEvent) => void) | undefined);
20
+ execute(args: ExecuteArgs): Promise<ToolResult>;
21
+ private emit;
22
+ }
23
+ export {};
@@ -0,0 +1,169 @@
1
+ import { createHash } from "node:crypto";
2
+ import { validateAgainstSchema } from "./schema-validator.js";
3
+ const DEFAULT_POLICY = {
4
+ autoRunRead: true,
5
+ requireApprovalForWrite: true
6
+ };
7
+ function sleep(ms) {
8
+ return new Promise((resolve) => setTimeout(resolve, ms));
9
+ }
10
+ function timeoutPromise(ms) {
11
+ return new Promise((_resolve, reject) => {
12
+ const t = setTimeout(() => {
13
+ clearTimeout(t);
14
+ reject(new Error(`Tool timed out after ${ms}ms`));
15
+ }, ms);
16
+ });
17
+ }
18
+ function normalizedToolError(code, message, retryable) {
19
+ return {
20
+ ok: false,
21
+ error: { code, message, retryable }
22
+ };
23
+ }
24
+ function stableHash(value) {
25
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
26
+ }
27
+ export class ToolExecutor {
28
+ registry;
29
+ logger;
30
+ onExecution;
31
+ idempotencyCache = new Map();
32
+ constructor(registry, logger, onExecution) {
33
+ this.registry = registry;
34
+ this.logger = logger;
35
+ this.onExecution = onExecution;
36
+ }
37
+ async execute(args) {
38
+ const tool = this.registry.getByName(args.toolName);
39
+ const startedAt = Date.now();
40
+ const policy = {
41
+ autoRunRead: args.policy?.autoRunRead ?? DEFAULT_POLICY.autoRunRead,
42
+ requireApprovalForWrite: args.policy?.requireApprovalForWrite ?? DEFAULT_POLICY.requireApprovalForWrite
43
+ };
44
+ if (!tool || tool.manifest.enabled === false) {
45
+ return {
46
+ ...normalizedToolError("tool_not_found", `Tool not found: ${args.toolName}`, false),
47
+ latencyMs: Date.now() - startedAt,
48
+ attempts: 1
49
+ };
50
+ }
51
+ if (tool.manifest.capability === "write" && policy.requireApprovalForWrite) {
52
+ return {
53
+ ...normalizedToolError("tool_requires_approval", `Tool requires approval: ${tool.manifest.name}`, false),
54
+ latencyMs: Date.now() - startedAt,
55
+ attempts: 1
56
+ };
57
+ }
58
+ if (tool.manifest.capability === "read" && !policy.autoRunRead) {
59
+ return {
60
+ ...normalizedToolError("tool_auto_run_disabled", `Read tool auto-run disabled: ${tool.manifest.name}`, false),
61
+ latencyMs: Date.now() - startedAt,
62
+ attempts: 1
63
+ };
64
+ }
65
+ const inputValidation = validateAgainstSchema(args.input, tool.manifest.inputSchema);
66
+ if (!inputValidation.ok) {
67
+ return {
68
+ ...normalizedToolError("invalid_tool_input", inputValidation.message, false),
69
+ latencyMs: Date.now() - startedAt,
70
+ attempts: 1
71
+ };
72
+ }
73
+ const idempotencyKey = `${args.context.traceId}:${tool.manifest.name}:${stableHash(args.input)}`;
74
+ if (tool.manifest.idempotent && this.idempotencyCache.has(idempotencyKey)) {
75
+ return this.idempotencyCache.get(idempotencyKey);
76
+ }
77
+ const maxAttempts = Math.max(1, tool.manifest.retryPolicy.maxAttempts);
78
+ const backoffMs = Math.max(0, tool.manifest.retryPolicy.backoffMs ?? 0);
79
+ let attempts = 0;
80
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
81
+ attempts = attempt;
82
+ try {
83
+ const combinedSignal = args.signal
84
+ ? AbortSignal.any([args.signal, AbortSignal.timeout(tool.manifest.timeoutMs)])
85
+ : AbortSignal.timeout(tool.manifest.timeoutMs);
86
+ // Defense-in-depth: race the handler against a hard timeout Promise.
87
+ // `combinedSignal` is passed to the handler so well-behaved tools can
88
+ // abort their underlying I/O, but handlers that forget to wire the
89
+ // signal through (or underlying SDKs that ignore it) would otherwise
90
+ // hang forever. The race guarantees we always reject after timeoutMs.
91
+ const output = await Promise.race([
92
+ tool.handler({ input: args.input, context: args.context, manifest: tool.manifest, signal: combinedSignal }),
93
+ timeoutPromise(tool.manifest.timeoutMs)
94
+ ]);
95
+ const outputValidation = validateAgainstSchema(output, tool.manifest.outputSchema);
96
+ if (!outputValidation.ok) {
97
+ const result = {
98
+ ...normalizedToolError("invalid_tool_output", outputValidation.message, false),
99
+ latencyMs: Date.now() - startedAt,
100
+ attempts
101
+ };
102
+ this.emit(args, result);
103
+ return result;
104
+ }
105
+ const result = {
106
+ ok: true,
107
+ data: output,
108
+ latencyMs: Date.now() - startedAt,
109
+ attempts
110
+ };
111
+ if (tool.manifest.idempotent)
112
+ this.idempotencyCache.set(idempotencyKey, result);
113
+ this.emit(args, result);
114
+ return result;
115
+ }
116
+ catch (error) {
117
+ const message = error instanceof Error ? error.message : String(error);
118
+ const retryable = attempt < maxAttempts;
119
+ if (retryable && backoffMs > 0)
120
+ await sleep(backoffMs * attempt);
121
+ if (!retryable) {
122
+ const result = {
123
+ ...normalizedToolError("tool_execution_failed", message.slice(0, 240), false),
124
+ latencyMs: Date.now() - startedAt,
125
+ attempts
126
+ };
127
+ this.emit(args, result);
128
+ return result;
129
+ }
130
+ }
131
+ }
132
+ const result = {
133
+ ...normalizedToolError("tool_execution_failed", "Tool execution failed", false),
134
+ latencyMs: Date.now() - startedAt,
135
+ attempts
136
+ };
137
+ this.emit(args, result);
138
+ return result;
139
+ }
140
+ emit(args, result) {
141
+ const payload = {
142
+ event: "tool_execution",
143
+ tool: args.toolName,
144
+ ok: result.ok,
145
+ latencyMs: result.latencyMs,
146
+ attempts: result.attempts,
147
+ traceId: args.context.traceId,
148
+ siteId: args.context.siteId,
149
+ sessionId: args.context.sessionId,
150
+ plannerProvider: args.context.plannerProvider,
151
+ errorCode: result.error?.code
152
+ };
153
+ if (result.ok)
154
+ this.logger.info(payload, "Tool executed");
155
+ else
156
+ this.logger.warn(payload, "Tool execution failed");
157
+ this.onExecution?.({
158
+ toolName: args.toolName,
159
+ ok: result.ok,
160
+ latencyMs: result.latencyMs,
161
+ attempts: result.attempts,
162
+ errorCode: result.error?.code,
163
+ traceId: args.context.traceId,
164
+ sessionId: args.context.sessionId,
165
+ siteId: args.context.siteId,
166
+ plannerProvider: args.context.plannerProvider
167
+ });
168
+ }
169
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./types.ts";
2
+ export * from "./schema-validator.ts";
3
+ export * from "./registry.ts";
4
+ export * from "./executor.ts";
5
+ export * from "./runtime.ts";
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export * from "./schema-validator.js";
3
+ export * from "./registry.js";
4
+ export * from "./executor.js";
5
+ export * from "./runtime.js";
@@ -0,0 +1,21 @@
1
+ import type { ToolManifest, RegisteredTool, ToolHandler, RemoteToolRegistration } from "./types.ts";
2
+ export declare class ToolRegistry {
3
+ private readonly tools;
4
+ registerBuiltin(manifest: ToolManifest, handler: ToolHandler): void;
5
+ registerRemote(args: RemoteToolRegistration): void;
6
+ getByName(name: string): RegisteredTool | undefined;
7
+ listEnabled(): RegisteredTool[];
8
+ listManifests(): {
9
+ source: "builtin" | "remote";
10
+ endpoint: string | undefined;
11
+ name: string;
12
+ description: string;
13
+ inputSchema: import("./types.ts").JsonSchema;
14
+ outputSchema: import("./types.ts").JsonSchema;
15
+ capability: import("./types.ts").ToolCapability;
16
+ timeoutMs: number;
17
+ retryPolicy: import("./types.ts").ToolRetryPolicy;
18
+ idempotent: boolean;
19
+ enabled?: boolean;
20
+ }[];
21
+ }
@@ -0,0 +1,75 @@
1
+ import { validateToolManifestShape } from "./schema-validator.js";
2
+ function normalizeToolName(name) {
3
+ return name.trim().toLowerCase();
4
+ }
5
+ export class ToolRegistry {
6
+ tools = new Map();
7
+ registerBuiltin(manifest, handler) {
8
+ const valid = validateToolManifestShape(manifest);
9
+ if (!valid.ok)
10
+ throw new Error(valid.message);
11
+ const key = normalizeToolName(manifest.name);
12
+ this.tools.set(key, {
13
+ manifest: { ...manifest, enabled: manifest.enabled ?? true },
14
+ handler,
15
+ source: "builtin"
16
+ });
17
+ }
18
+ registerRemote(args) {
19
+ const valid = validateToolManifestShape(args.manifest);
20
+ if (!valid.ok)
21
+ throw new Error(valid.message);
22
+ const endpoint = args.endpoint.trim();
23
+ if (!/^https?:\/\//i.test(endpoint))
24
+ throw new Error("remote tool endpoint must be http(s)");
25
+ const key = normalizeToolName(args.manifest.name);
26
+ const handler = async ({ input, context, signal }) => {
27
+ const response = await fetch(endpoint, {
28
+ method: "POST",
29
+ headers: {
30
+ "content-type": "application/json",
31
+ ...(args.staticHeaders ?? {}),
32
+ ...(context.authHeader ? { authorization: context.authHeader } : {})
33
+ },
34
+ body: JSON.stringify({
35
+ toolName: args.manifest.name,
36
+ arguments: input,
37
+ context: {
38
+ siteId: context.siteId,
39
+ sessionId: context.sessionId,
40
+ userId: context.userId,
41
+ traceId: context.traceId,
42
+ plannerProvider: context.plannerProvider
43
+ }
44
+ }),
45
+ signal,
46
+ });
47
+ if (!response.ok) {
48
+ const body = await response.text().catch(() => "");
49
+ throw new Error(`Remote tool failed (${response.status}): ${body.slice(0, 200)}`);
50
+ }
51
+ const payload = (await response.json());
52
+ return payload.data;
53
+ };
54
+ this.tools.set(key, {
55
+ manifest: { ...args.manifest, enabled: args.manifest.enabled ?? true },
56
+ handler,
57
+ source: "remote",
58
+ endpoint,
59
+ staticHeaders: args.staticHeaders
60
+ });
61
+ }
62
+ getByName(name) {
63
+ return this.tools.get(normalizeToolName(name));
64
+ }
65
+ listEnabled() {
66
+ return Array.from(this.tools.values()).filter((tool) => tool.manifest.enabled !== false);
67
+ }
68
+ listManifests() {
69
+ return this.listEnabled().map((entry) => ({
70
+ ...entry.manifest,
71
+ source: entry.source,
72
+ endpoint: entry.endpoint
73
+ }));
74
+ }
75
+ }
@@ -0,0 +1,27 @@
1
+ import type { ToolExecutionEvent, ToolExecutionPolicy, ToolCallContext } from "./types.ts";
2
+ import { ToolRegistry } from "./registry.ts";
3
+ import { ToolExecutor } from "./executor.ts";
4
+ type Logger = {
5
+ info: (payload: Record<string, unknown>, message?: string) => void;
6
+ warn: (payload: Record<string, unknown>, message?: string) => void;
7
+ error: (payload: Record<string, unknown>, message?: string) => void;
8
+ };
9
+ export type ToolRuntime = {
10
+ registry: ToolRegistry;
11
+ executor: ToolExecutor;
12
+ defaultPolicy: ToolExecutionPolicy;
13
+ };
14
+ type CreateToolRuntimeArgs = {
15
+ logger: Logger;
16
+ onExecution?: (event: ToolExecutionEvent) => void;
17
+ };
18
+ export declare function createToolRuntime(args: CreateToolRuntimeArgs): Promise<ToolRuntime>;
19
+ export declare function loadRemoteToolRegistrations(registry: ToolRegistry, logger: Logger): Promise<void>;
20
+ export declare function executeToolCall(args: {
21
+ runtime: ToolRuntime;
22
+ toolName: string;
23
+ input: unknown;
24
+ context: ToolCallContext;
25
+ policy?: Partial<ToolExecutionPolicy>;
26
+ }): Promise<import("./types.ts").ToolResult>;
27
+ export {};
@@ -0,0 +1,48 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { ToolRegistry } from "./registry.js";
4
+ import { ToolExecutor } from "./executor.js";
5
+ export async function createToolRuntime(args) {
6
+ const registry = new ToolRegistry();
7
+ const defaultPolicy = {
8
+ autoRunRead: true,
9
+ requireApprovalForWrite: true
10
+ };
11
+ const executor = new ToolExecutor(registry, args.logger, args.onExecution);
12
+ await loadRemoteToolRegistrations(registry, args.logger);
13
+ return { registry, executor, defaultPolicy };
14
+ }
15
+ export async function loadRemoteToolRegistrations(registry, logger) {
16
+ const manifestPath = process.env.ORCHESTRATOR_TOOL_MANIFEST_PATH?.trim();
17
+ if (!manifestPath)
18
+ return;
19
+ if (!existsSync(manifestPath)) {
20
+ logger.warn({ manifestPath }, "Tool manifest path not found; skipping remote tool registration");
21
+ return;
22
+ }
23
+ try {
24
+ const raw = await readFile(manifestPath, "utf8");
25
+ const parsed = JSON.parse(raw);
26
+ const items = Array.isArray(parsed.tools) ? parsed.tools : [];
27
+ for (const item of items) {
28
+ registry.registerRemote({
29
+ manifest: item.manifest,
30
+ endpoint: item.endpoint,
31
+ staticHeaders: item.staticHeaders
32
+ });
33
+ }
34
+ logger.info({ manifestPath, count: items.length }, "Loaded remote tool registrations");
35
+ }
36
+ catch (error) {
37
+ const detail = error instanceof Error ? error.message : String(error);
38
+ logger.error({ err: detail, manifestPath }, "Failed to load remote tool registrations");
39
+ }
40
+ }
41
+ export async function executeToolCall(args) {
42
+ return args.runtime.executor.execute({
43
+ toolName: args.toolName,
44
+ input: args.input,
45
+ context: args.context,
46
+ policy: args.policy
47
+ });
48
+ }
@@ -0,0 +1,24 @@
1
+ import type { JsonSchema } from "./types.ts";
2
+ export type SchemaValidationResult = {
3
+ ok: true;
4
+ } | {
5
+ ok: false;
6
+ message: string;
7
+ };
8
+ export declare function validateAgainstSchema(value: unknown, schema: JsonSchema, path?: string): SchemaValidationResult;
9
+ export declare function validateToolManifestShape(manifest: {
10
+ name?: unknown;
11
+ description?: unknown;
12
+ capability?: unknown;
13
+ timeoutMs?: unknown;
14
+ retryPolicy?: unknown;
15
+ idempotent?: unknown;
16
+ inputSchema?: unknown;
17
+ outputSchema?: unknown;
18
+ }): {
19
+ ok: false;
20
+ message: string;
21
+ } | {
22
+ ok: true;
23
+ message?: undefined;
24
+ };
@@ -0,0 +1,88 @@
1
+ function typeMatches(schemaType, value) {
2
+ if (schemaType === "string")
3
+ return typeof value === "string";
4
+ if (schemaType === "number")
5
+ return typeof value === "number" && Number.isFinite(value);
6
+ if (schemaType === "integer")
7
+ return typeof value === "number" && Number.isInteger(value);
8
+ if (schemaType === "boolean")
9
+ return typeof value === "boolean";
10
+ if (schemaType === "null")
11
+ return value === null;
12
+ if (schemaType === "array")
13
+ return Array.isArray(value);
14
+ if (schemaType === "object")
15
+ return value !== null && typeof value === "object" && !Array.isArray(value);
16
+ return true;
17
+ }
18
+ function isPlainObject(value) {
19
+ return value !== null && typeof value === "object" && !Array.isArray(value);
20
+ }
21
+ export function validateAgainstSchema(value, schema, path = "$") {
22
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
23
+ if (!schema.enum.some((entry) => Object.is(entry, value))) {
24
+ return { ok: false, message: `${path}: value is not in enum` };
25
+ }
26
+ }
27
+ if (schema.type && !typeMatches(schema.type, value)) {
28
+ return { ok: false, message: `${path}: expected ${schema.type}` };
29
+ }
30
+ if (schema.type === "array") {
31
+ if (!Array.isArray(value))
32
+ return { ok: false, message: `${path}: expected array` };
33
+ if (schema.items) {
34
+ for (let idx = 0; idx < value.length; idx += 1) {
35
+ const result = validateAgainstSchema(value[idx], schema.items, `${path}[${idx}]`);
36
+ if (!result.ok)
37
+ return result;
38
+ }
39
+ }
40
+ return { ok: true };
41
+ }
42
+ if (schema.type === "object") {
43
+ if (!isPlainObject(value))
44
+ return { ok: false, message: `${path}: expected object` };
45
+ const required = Array.isArray(schema.required) ? schema.required : [];
46
+ for (const key of required) {
47
+ if (!(key in value))
48
+ return { ok: false, message: `${path}.${key}: required` };
49
+ }
50
+ const props = schema.properties ?? {};
51
+ for (const [key, propSchema] of Object.entries(props)) {
52
+ if (!(key in value))
53
+ continue;
54
+ const result = validateAgainstSchema(value[key], propSchema, `${path}.${key}`);
55
+ if (!result.ok)
56
+ return result;
57
+ }
58
+ if (schema.additionalProperties === false) {
59
+ for (const key of Object.keys(value)) {
60
+ if (!(key in props))
61
+ return { ok: false, message: `${path}.${key}: additional property is not allowed` };
62
+ }
63
+ }
64
+ }
65
+ return { ok: true };
66
+ }
67
+ export function validateToolManifestShape(manifest) {
68
+ if (typeof manifest.name !== "string" || manifest.name.trim().length === 0)
69
+ return { ok: false, message: "manifest.name is required" };
70
+ if (typeof manifest.description !== "string" || manifest.description.trim().length === 0)
71
+ return { ok: false, message: "manifest.description is required" };
72
+ if (manifest.capability !== "read" && manifest.capability !== "write")
73
+ return { ok: false, message: "manifest.capability must be read or write" };
74
+ if (typeof manifest.timeoutMs !== "number" || !Number.isFinite(manifest.timeoutMs) || manifest.timeoutMs < 250)
75
+ return { ok: false, message: "manifest.timeoutMs must be >= 250" };
76
+ if (!isPlainObject(manifest.retryPolicy))
77
+ return { ok: false, message: "manifest.retryPolicy is required" };
78
+ if (typeof manifest.retryPolicy.maxAttempts !== "number" || !Number.isInteger(manifest.retryPolicy.maxAttempts) || manifest.retryPolicy.maxAttempts < 1 || manifest.retryPolicy.maxAttempts > 5) {
79
+ return { ok: false, message: "manifest.retryPolicy.maxAttempts must be an integer between 1 and 5" };
80
+ }
81
+ if (typeof manifest.idempotent !== "boolean")
82
+ return { ok: false, message: "manifest.idempotent must be boolean" };
83
+ if (!isPlainObject(manifest.inputSchema))
84
+ return { ok: false, message: "manifest.inputSchema is required" };
85
+ if (!isPlainObject(manifest.outputSchema))
86
+ return { ok: false, message: "manifest.outputSchema is required" };
87
+ return { ok: true };
88
+ }
@@ -0,0 +1,86 @@
1
+ export type JsonSchema = {
2
+ type?: "object" | "array" | "string" | "number" | "integer" | "boolean" | "null";
3
+ description?: string;
4
+ enum?: unknown[];
5
+ properties?: Record<string, JsonSchema>;
6
+ required?: string[];
7
+ additionalProperties?: boolean;
8
+ items?: JsonSchema;
9
+ };
10
+ export type ToolCapability = "read" | "write";
11
+ export type ToolRetryPolicy = {
12
+ maxAttempts: number;
13
+ backoffMs?: number;
14
+ };
15
+ export type ToolManifest = {
16
+ name: string;
17
+ description: string;
18
+ inputSchema: JsonSchema;
19
+ outputSchema: JsonSchema;
20
+ capability: ToolCapability;
21
+ timeoutMs: number;
22
+ retryPolicy: ToolRetryPolicy;
23
+ idempotent: boolean;
24
+ enabled?: boolean;
25
+ };
26
+ export type ToolCallContext = {
27
+ siteId: string;
28
+ sessionId: string;
29
+ userId?: string;
30
+ traceId: string;
31
+ plannerProvider: "anthropic" | "openai" | "gemini" | "demo";
32
+ authHeader?: string;
33
+ gdriveFolderId?: string;
34
+ onStatusUpdate?: (message: string) => void;
35
+ onImageProgress?: (event: {
36
+ percent: number;
37
+ stage: string;
38
+ }) => void;
39
+ };
40
+ export type ToolError = {
41
+ code: string;
42
+ message: string;
43
+ retryable: boolean;
44
+ };
45
+ export type ToolResult = {
46
+ ok: boolean;
47
+ data?: unknown;
48
+ error?: ToolError;
49
+ latencyMs: number;
50
+ attempts: number;
51
+ };
52
+ export type ToolExecutionPolicy = {
53
+ autoRunRead: boolean;
54
+ requireApprovalForWrite: boolean;
55
+ };
56
+ export type ToolExecutionEvent = {
57
+ toolName: string;
58
+ ok: boolean;
59
+ latencyMs: number;
60
+ attempts: number;
61
+ errorCode?: string;
62
+ traceId: string;
63
+ sessionId: string;
64
+ siteId: string;
65
+ plannerProvider: string;
66
+ /** True when the tool was deferred (placeholder returned) to avoid blocking text streaming */
67
+ deferred?: boolean;
68
+ };
69
+ export type ToolHandler = (args: {
70
+ input: unknown;
71
+ context: ToolCallContext;
72
+ manifest: ToolManifest;
73
+ signal?: AbortSignal;
74
+ }) => Promise<unknown>;
75
+ export type RegisteredTool = {
76
+ manifest: ToolManifest;
77
+ handler: ToolHandler;
78
+ source: "builtin" | "remote";
79
+ endpoint?: string;
80
+ staticHeaders?: Record<string, string>;
81
+ };
82
+ export type RemoteToolRegistration = {
83
+ manifest: ToolManifest;
84
+ endpoint: string;
85
+ staticHeaders?: Record<string, string>;
86
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ export type UnsplashImage = {
2
+ url: string;
3
+ alt: string;
4
+ query: string;
5
+ };
6
+ export type UnsplashResolveOptions = {
7
+ variationIndex?: number;
8
+ subjectKeywords?: string[];
9
+ usedImageUrls?: Set<string>;
10
+ };
11
+ type ResolveUnsplashImage = (query: string, options?: UnsplashResolveOptions) => Promise<UnsplashImage | null>;
12
+ export declare function resolveDistinctUnsplashImage(args: {
13
+ query: string;
14
+ variationIndex: number;
15
+ usedImageUrls: Set<string>;
16
+ resolveImage: ResolveUnsplashImage;
17
+ maxAttempts?: number;
18
+ }): Promise<UnsplashImage | null>;
19
+ export {};