let-them-talk 5.3.0 → 5.4.1

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 (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +346 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +864 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +141 -34
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9577 -7216
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
@@ -0,0 +1,79 @@
1
+ // DALL-E 3 provider adapter (Phase 5 — placeholder)
2
+ const https = require('https');
3
+
4
+ class DalleProvider {
5
+ constructor(options = {}) {
6
+ this.apiKey = options.apiKey || '';
7
+ this.model = options.model || 'dall-e-3';
8
+ this.size = options.size || '1024x1024';
9
+ this.quality = options.quality || 'standard';
10
+ this.name = 'dalle';
11
+ this.color = '#10b981'; // green
12
+ }
13
+
14
+ async generate(prompt, options = {}) {
15
+ if (!this.apiKey) throw new Error('DALL-E API key not configured');
16
+
17
+ const body = JSON.stringify({
18
+ model: this.model,
19
+ prompt: prompt,
20
+ n: 1,
21
+ size: options.size || this.size,
22
+ quality: options.quality || this.quality,
23
+ response_format: 'b64_json',
24
+ });
25
+
26
+ return new Promise((resolve, reject) => {
27
+ const req = https.request({
28
+ hostname: 'api.openai.com',
29
+ path: '/v1/images/generations',
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ 'Authorization': `Bearer ${this.apiKey}`,
34
+ },
35
+ timeout: 120000,
36
+ }, (res) => {
37
+ let data = '';
38
+ res.on('data', chunk => data += chunk);
39
+ res.on('end', () => {
40
+ try {
41
+ const result = JSON.parse(data);
42
+ if (result.error) {
43
+ reject(new Error(result.error.message || 'DALL-E API error'));
44
+ return;
45
+ }
46
+ if (result.data && result.data[0]) {
47
+ resolve({
48
+ type: 'image',
49
+ data: result.data[0].b64_json,
50
+ format: 'png',
51
+ model: this.model,
52
+ prompt: prompt,
53
+ revised_prompt: result.data[0].revised_prompt,
54
+ });
55
+ } else {
56
+ reject(new Error('No image data in response'));
57
+ }
58
+ } catch (e) {
59
+ reject(new Error('Failed to parse DALL-E response: ' + e.message));
60
+ }
61
+ });
62
+ });
63
+ req.on('error', (e) => reject(new Error('DALL-E connection failed: ' + e.message)));
64
+ req.on('timeout', () => { req.destroy(); reject(new Error('DALL-E request timed out')); });
65
+ req.write(body);
66
+ req.end();
67
+ });
68
+ }
69
+
70
+ async checkHealth() {
71
+ return !!this.apiKey;
72
+ }
73
+
74
+ async listModels() {
75
+ return [{ name: 'dall-e-3', size: 'cloud' }, { name: 'dall-e-2', size: 'cloud' }];
76
+ }
77
+ }
78
+
79
+ module.exports = { DalleProvider };
@@ -0,0 +1,181 @@
1
+ // Gemini API provider — text + image generation via Google's REST API
2
+ // Supports gemini-2.5-flash-image and gemini-3.1-flash-image-preview models
3
+ // Uses raw HTTP (no SDK dependency) for maximum compatibility
4
+
5
+ const https = require('https');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const API_HOST = 'generativelanguage.googleapis.com';
10
+ const API_BASE = '/v1beta/models/';
11
+
12
+ class GeminiProvider {
13
+ constructor(options = {}) {
14
+ this.apiKey = options.apiKey || '';
15
+ this.model = options.model || 'gemini-3-pro-image-preview';
16
+ this.name = 'gemini';
17
+ this.color = '#4285f4'; // Google blue
18
+ }
19
+
20
+ async generate(prompt, options = {}) {
21
+ if (!this.apiKey) throw new Error('Gemini API key not configured');
22
+
23
+ var aspectRatio = options.aspectRatio || '16:9';
24
+
25
+ // Build request parts — text + optional input images
26
+ var parts = [{ text: prompt }];
27
+
28
+ // Add input images if provided (for image-to-image / style reference)
29
+ if (options.images && Array.isArray(options.images)) {
30
+ for (var ii = 0; ii < options.images.length; ii++) {
31
+ var img = options.images[ii];
32
+ if (img.base64 && img.mimeType) {
33
+ parts.push({
34
+ inlineData: {
35
+ mimeType: img.mimeType,
36
+ data: img.base64,
37
+ }
38
+ });
39
+ }
40
+ }
41
+ }
42
+
43
+ var body = {
44
+ contents: [{ parts: parts }],
45
+ generationConfig: {
46
+ responseModalities: ['TEXT', 'IMAGE'],
47
+ imageConfig: {
48
+ aspectRatio: aspectRatio,
49
+ }
50
+ }
51
+ };
52
+
53
+ // gemini-3.x models support imageSize — default to highest quality
54
+ if (this.model.indexOf('gemini-3') === 0) {
55
+ body.generationConfig.imageConfig.imageSize = options.imageSize || '4K';
56
+ }
57
+
58
+ var bodyStr = JSON.stringify(body);
59
+ var apiPath = API_BASE + this.model + ':generateContent';
60
+ var self = this;
61
+
62
+ return new Promise(function(resolve, reject) {
63
+ var req = https.request({
64
+ hostname: API_HOST,
65
+ path: apiPath,
66
+ method: 'POST',
67
+ headers: {
68
+ 'Content-Type': 'application/json',
69
+ 'x-goog-api-key': self.apiKey,
70
+ },
71
+ timeout: 180000, // 3 min — image gen can be slow
72
+ }, function(res) {
73
+ var data = '';
74
+ res.on('data', function(chunk) { data += chunk; });
75
+ res.on('end', function() {
76
+ try {
77
+ var result = JSON.parse(data);
78
+
79
+ // Check for API errors
80
+ if (result.error) {
81
+ reject(new Error('Gemini API error: ' + (result.error.message || JSON.stringify(result.error))));
82
+ return;
83
+ }
84
+
85
+ // Parse response — look for image parts
86
+ if (!result.candidates || !result.candidates[0] || !result.candidates[0].content) {
87
+ reject(new Error('No content in Gemini response'));
88
+ return;
89
+ }
90
+
91
+ var parts = result.candidates[0].content.parts || [];
92
+ var imageData = null;
93
+ var mimeType = 'image/png';
94
+ var textResponse = '';
95
+
96
+ for (var i = 0; i < parts.length; i++) {
97
+ var part = parts[i];
98
+ if (part.inlineData && part.inlineData.data) {
99
+ imageData = part.inlineData.data; // base64
100
+ mimeType = part.inlineData.mimeType || 'image/png';
101
+ } else if (part.text) {
102
+ textResponse += part.text;
103
+ }
104
+ }
105
+
106
+ if (imageData) {
107
+ // Determine file extension from mime type
108
+ var ext = 'png';
109
+ if (mimeType.indexOf('jpeg') !== -1 || mimeType.indexOf('jpg') !== -1) ext = 'jpg';
110
+ else if (mimeType.indexOf('webp') !== -1) ext = 'webp';
111
+
112
+ resolve({
113
+ type: 'image',
114
+ data: imageData,
115
+ format: ext,
116
+ model: self.model,
117
+ prompt: prompt,
118
+ revised_prompt: textResponse || null,
119
+ });
120
+ } else if (textResponse) {
121
+ // Text-only response (no image generated)
122
+ resolve({
123
+ type: 'text',
124
+ data: textResponse,
125
+ model: self.model,
126
+ prompt: prompt,
127
+ });
128
+ } else {
129
+ reject(new Error('Empty response from Gemini — no image or text returned'));
130
+ }
131
+ } catch (e) {
132
+ reject(new Error('Failed to parse Gemini response: ' + e.message));
133
+ }
134
+ });
135
+ });
136
+
137
+ req.on('error', function(e) {
138
+ reject(new Error('Gemini connection failed: ' + e.message));
139
+ });
140
+ req.on('timeout', function() {
141
+ req.destroy();
142
+ reject(new Error('Gemini request timed out (180s)'));
143
+ });
144
+ req.write(bodyStr);
145
+ req.end();
146
+ });
147
+ }
148
+
149
+ async checkHealth() {
150
+ if (!this.apiKey) return false;
151
+ // Quick check: list models endpoint
152
+ var self = this;
153
+ return new Promise(function(resolve) {
154
+ var req = https.request({
155
+ hostname: API_HOST,
156
+ path: '/v1beta/models?key=' + self.apiKey,
157
+ method: 'GET',
158
+ timeout: 10000,
159
+ }, function(res) {
160
+ var data = '';
161
+ res.on('data', function(chunk) { data += chunk; });
162
+ res.on('end', function() {
163
+ resolve(res.statusCode === 200);
164
+ });
165
+ });
166
+ req.on('error', function() { resolve(false); });
167
+ req.on('timeout', function() { req.destroy(); resolve(false); });
168
+ req.end();
169
+ });
170
+ }
171
+
172
+ async listModels() {
173
+ return [
174
+ { name: 'gemini-3-pro-image-preview', size: 'cloud', description: 'Pro quality, 4K, Nano Banana' },
175
+ { name: 'gemini-3.1-flash-image-preview', size: 'cloud', description: 'Fast, supports 2K + imageSize' },
176
+ { name: 'gemini-2.5-flash-image', size: 'cloud', description: 'Legacy fast image generation' },
177
+ ];
178
+ }
179
+ }
180
+
181
+ module.exports = { GeminiProvider };
@@ -0,0 +1,184 @@
1
+ // Ollama provider adapter for local AI model inference
2
+ // Supports text-to-image models (SDXL, Flux, etc.) and vision models
3
+
4
+ const http = require('http');
5
+ const https = require('https');
6
+
7
+ class OllamaProvider {
8
+ constructor(options = {}) {
9
+ this.endpoint = options.endpoint || 'http://localhost:11434';
10
+ this.model = options.model || 'sdxl';
11
+ this.name = 'ollama';
12
+ this.color = '#0ea5e9'; // blue
13
+ }
14
+
15
+ async generate(prompt, options = {}) {
16
+ const url = new URL(this.endpoint);
17
+ const isHttps = url.protocol === 'https:';
18
+ const transport = isHttps ? https : http;
19
+
20
+ // Try image generation first (for models that support it)
21
+ const imageResult = await this._tryImageGeneration(transport, url, prompt, options);
22
+ if (imageResult) return imageResult;
23
+
24
+ // Fallback: use chat completion with image description
25
+ return this._textGeneration(transport, url, prompt, options);
26
+ }
27
+
28
+ async _tryImageGeneration(transport, url, prompt, options) {
29
+ // Ollama doesn't have a native image generation API yet,
30
+ // but some setups expose it via compatible endpoints.
31
+ // Try the /api/generate endpoint with image-capable models
32
+ const body = JSON.stringify({
33
+ model: this.model,
34
+ prompt: prompt,
35
+ stream: false,
36
+ options: {
37
+ num_predict: options.maxTokens || 4096,
38
+ }
39
+ });
40
+
41
+ return new Promise((resolve) => {
42
+ const req = transport.request({
43
+ hostname: url.hostname,
44
+ port: url.port || (url.protocol === 'https:' ? 443 : 11434),
45
+ path: '/api/generate',
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ timeout: 120000,
49
+ }, (res) => {
50
+ let data = '';
51
+ res.on('data', chunk => data += chunk);
52
+ res.on('end', () => {
53
+ try {
54
+ const result = JSON.parse(data);
55
+ // Check if response contains base64 image data
56
+ if (result.images && result.images.length > 0) {
57
+ resolve({
58
+ type: 'image',
59
+ data: result.images[0], // base64
60
+ format: 'png',
61
+ model: this.model,
62
+ prompt: prompt,
63
+ });
64
+ } else if (result.response) {
65
+ // Text-only response — model doesn't generate images
66
+ resolve({
67
+ type: 'text',
68
+ data: result.response,
69
+ model: this.model,
70
+ prompt: prompt,
71
+ });
72
+ } else {
73
+ resolve(null);
74
+ }
75
+ } catch {
76
+ resolve(null);
77
+ }
78
+ });
79
+ });
80
+ req.on('error', () => resolve(null));
81
+ req.on('timeout', () => { req.destroy(); resolve(null); });
82
+ req.write(body);
83
+ req.end();
84
+ });
85
+ }
86
+
87
+ async _textGeneration(transport, url, prompt, options) {
88
+ const body = JSON.stringify({
89
+ model: this.model,
90
+ prompt: `Generate a detailed visual description for: ${prompt}`,
91
+ stream: false,
92
+ });
93
+
94
+ return new Promise((resolve, reject) => {
95
+ const req = transport.request({
96
+ hostname: url.hostname,
97
+ port: url.port || (url.protocol === 'https:' ? 443 : 11434),
98
+ path: '/api/generate',
99
+ method: 'POST',
100
+ headers: { 'Content-Type': 'application/json' },
101
+ timeout: 120000,
102
+ }, (res) => {
103
+ let data = '';
104
+ res.on('data', chunk => data += chunk);
105
+ res.on('end', () => {
106
+ try {
107
+ const result = JSON.parse(data);
108
+ resolve({
109
+ type: 'text',
110
+ data: result.response || 'No response from model',
111
+ model: this.model,
112
+ prompt: prompt,
113
+ });
114
+ } catch (e) {
115
+ reject(new Error('Failed to parse Ollama response: ' + e.message));
116
+ }
117
+ });
118
+ });
119
+ req.on('error', (e) => reject(new Error('Ollama connection failed: ' + e.message)));
120
+ req.on('timeout', () => { req.destroy(); reject(new Error('Ollama request timed out')); });
121
+ req.write(body);
122
+ req.end();
123
+ });
124
+ }
125
+
126
+ async listModels() {
127
+ const url = new URL(this.endpoint);
128
+ const isHttps = url.protocol === 'https:';
129
+ const transport = isHttps ? https : http;
130
+
131
+ return new Promise((resolve) => {
132
+ const req = transport.request({
133
+ hostname: url.hostname,
134
+ port: url.port || (url.protocol === 'https:' ? 443 : 11434),
135
+ path: '/api/tags',
136
+ method: 'GET',
137
+ timeout: 10000,
138
+ }, (res) => {
139
+ let data = '';
140
+ res.on('data', chunk => data += chunk);
141
+ res.on('end', () => {
142
+ try {
143
+ const result = JSON.parse(data);
144
+ resolve((result.models || []).map(m => ({
145
+ name: m.name,
146
+ size: m.size,
147
+ modified: m.modified_at,
148
+ })));
149
+ } catch {
150
+ resolve([]);
151
+ }
152
+ });
153
+ });
154
+ req.on('error', () => resolve([]));
155
+ req.on('timeout', () => { req.destroy(); resolve([]); });
156
+ req.end();
157
+ });
158
+ }
159
+
160
+ async checkHealth() {
161
+ const url = new URL(this.endpoint);
162
+ const isHttps = url.protocol === 'https:';
163
+ const transport = isHttps ? https : http;
164
+
165
+ return new Promise((resolve) => {
166
+ const req = transport.request({
167
+ hostname: url.hostname,
168
+ port: url.port || (url.protocol === 'https:' ? 443 : 11434),
169
+ path: '/',
170
+ method: 'GET',
171
+ timeout: 5000,
172
+ }, (res) => {
173
+ let data = '';
174
+ res.on('data', chunk => data += chunk);
175
+ res.on('end', () => resolve(res.statusCode === 200));
176
+ });
177
+ req.on('error', () => resolve(false));
178
+ req.on('timeout', () => { req.destroy(); resolve(false); });
179
+ req.end();
180
+ });
181
+ }
182
+ }
183
+
184
+ module.exports = { OllamaProvider };
@@ -0,0 +1,115 @@
1
+ // Replicate provider adapter (Phase 5 — placeholder for Flux, Wan, SD)
2
+ const https = require('https');
3
+
4
+ class ReplicateProvider {
5
+ constructor(options = {}) {
6
+ this.apiKey = options.apiKey || '';
7
+ this.model = options.model || 'stability-ai/sdxl';
8
+ this.name = 'replicate';
9
+ this.color = '#8b5cf6'; // purple
10
+ }
11
+
12
+ async generate(prompt, options = {}) {
13
+ if (!this.apiKey) throw new Error('Replicate API key not configured');
14
+
15
+ const body = JSON.stringify({
16
+ version: this._getVersion(),
17
+ input: { prompt: prompt, ...options },
18
+ });
19
+
20
+ // Create prediction
21
+ const prediction = await new Promise((resolve, reject) => {
22
+ const req = https.request({
23
+ hostname: 'api.replicate.com',
24
+ path: '/v1/predictions',
25
+ method: 'POST',
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ 'Authorization': `Token ${this.apiKey}`,
29
+ },
30
+ timeout: 30000,
31
+ }, (res) => {
32
+ let data = '';
33
+ res.on('data', chunk => data += chunk);
34
+ res.on('end', () => {
35
+ try {
36
+ resolve(JSON.parse(data));
37
+ } catch (e) {
38
+ reject(new Error('Failed to parse Replicate response'));
39
+ }
40
+ });
41
+ });
42
+ req.on('error', (e) => reject(new Error('Replicate connection failed: ' + e.message)));
43
+ req.on('timeout', () => { req.destroy(); reject(new Error('Replicate request timed out')); });
44
+ req.write(body);
45
+ req.end();
46
+ });
47
+
48
+ if (prediction.error) throw new Error(prediction.error.detail || 'Replicate error');
49
+
50
+ // Poll for completion
51
+ const result = await this._pollResult(prediction.urls.get);
52
+ if (result.output && result.output.length > 0) {
53
+ return {
54
+ type: 'image',
55
+ data: result.output[0], // URL
56
+ format: 'url',
57
+ model: this.model,
58
+ prompt: prompt,
59
+ };
60
+ }
61
+ throw new Error('No output from Replicate');
62
+ }
63
+
64
+ async _pollResult(url, maxAttempts = 60) {
65
+ for (let i = 0; i < maxAttempts; i++) {
66
+ await new Promise(r => setTimeout(r, 2000));
67
+ const result = await new Promise((resolve, reject) => {
68
+ const parsedUrl = new URL(url);
69
+ const req = https.request({
70
+ hostname: parsedUrl.hostname,
71
+ path: parsedUrl.pathname,
72
+ method: 'GET',
73
+ headers: { 'Authorization': `Token ${this.apiKey}` },
74
+ timeout: 10000,
75
+ }, (res) => {
76
+ let data = '';
77
+ res.on('data', chunk => data += chunk);
78
+ res.on('end', () => {
79
+ try { resolve(JSON.parse(data)); } catch { reject(new Error('Parse error')); }
80
+ });
81
+ });
82
+ req.on('error', reject);
83
+ req.on('timeout', () => { req.destroy(); reject(new Error('Poll timeout')); });
84
+ req.end();
85
+ });
86
+ if (result.status === 'succeeded') return result;
87
+ if (result.status === 'failed' || result.status === 'canceled') {
88
+ throw new Error('Replicate prediction ' + result.status);
89
+ }
90
+ }
91
+ throw new Error('Replicate prediction timed out');
92
+ }
93
+
94
+ _getVersion() {
95
+ const versions = {
96
+ 'stability-ai/sdxl': '39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b',
97
+ 'black-forest-labs/flux': 'latest',
98
+ };
99
+ return versions[this.model] || 'latest';
100
+ }
101
+
102
+ async checkHealth() {
103
+ return !!this.apiKey;
104
+ }
105
+
106
+ async listModels() {
107
+ return [
108
+ { name: 'stability-ai/sdxl', size: 'cloud' },
109
+ { name: 'black-forest-labs/flux-schnell', size: 'cloud' },
110
+ { name: 'black-forest-labs/flux-dev', size: 'cloud' },
111
+ ];
112
+ }
113
+ }
114
+
115
+ module.exports = { ReplicateProvider };