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.
- package/CHANGELOG.md +3 -1
- package/README.md +346 -592
- package/SECURITY.md +3 -3
- package/USAGE.md +151 -0
- package/agent-contracts.js +447 -0
- package/api-agents.js +760 -0
- package/autonomy/decision-v2.js +380 -0
- package/autonomy/watchdog-policy.js +572 -0
- package/cli.js +454 -298
- package/conversation-templates/autonomous-feature.json +83 -22
- package/conversation-templates/code-review.json +69 -21
- package/conversation-templates/debug-squad.json +69 -21
- package/conversation-templates/feature-build.json +69 -21
- package/conversation-templates/research-write.json +69 -21
- package/dashboard.html +3148 -174
- package/dashboard.js +864 -786
- package/data-dir.js +58 -0
- package/docs/architecture/branch-semantics.md +157 -0
- package/docs/architecture/canonical-event-schema.md +88 -0
- package/docs/architecture/markdown-workspace.md +183 -0
- package/docs/architecture/runtime-contract.md +459 -0
- package/docs/architecture/runtime-migration-hardening.md +64 -0
- package/events/hooks.js +154 -0
- package/events/log.js +457 -0
- package/events/replay.js +33 -0
- package/events/schema.js +432 -0
- package/managed-team-integration.js +261 -0
- package/office/agents.js +704 -597
- package/office/animation.js +1 -1
- package/office/assets/arcade-cabinet.js +141 -0
- package/office/assets/archway.js +77 -0
- package/office/assets/bar-counter.js +91 -0
- package/office/assets/bar-stool.js +71 -0
- package/office/assets/beanbag.js +64 -0
- package/office/assets/bench.js +99 -0
- package/office/assets/bollard.js +87 -0
- package/office/assets/cactus.js +100 -0
- package/office/assets/carpet-tile.js +46 -0
- package/office/assets/chair.js +123 -0
- package/office/assets/chandelier.js +107 -0
- package/office/assets/coffee-machine.js +95 -0
- package/office/assets/coffee-table.js +81 -0
- package/office/assets/column.js +95 -0
- package/office/assets/desk-lamp.js +102 -0
- package/office/assets/desk.js +76 -0
- package/office/assets/dining-table.js +105 -0
- package/office/assets/door.js +70 -0
- package/office/assets/dual-monitor.js +72 -0
- package/office/assets/fence.js +76 -0
- package/office/assets/filing-cabinet.js +111 -0
- package/office/assets/floor-lamp.js +69 -0
- package/office/assets/floor-tile.js +54 -0
- package/office/assets/flower-pot.js +76 -0
- package/office/assets/foosball.js +95 -0
- package/office/assets/fridge.js +99 -0
- package/office/assets/gaming-chair.js +154 -0
- package/office/assets/gaming-desk.js +105 -0
- package/office/assets/glass-door.js +72 -0
- package/office/assets/glass-wall.js +64 -0
- package/office/assets/half-wall.js +49 -0
- package/office/assets/hanging-plant.js +112 -0
- package/office/assets/index.js +151 -0
- package/office/assets/indoor-tree.js +90 -0
- package/office/assets/l-sofa.js +153 -0
- package/office/assets/marble-floor.js +64 -0
- package/office/assets/materials.js +40 -0
- package/office/assets/meeting-table.js +88 -0
- package/office/assets/microwave.js +94 -0
- package/office/assets/monitor.js +67 -0
- package/office/assets/neon-strip.js +73 -0
- package/office/assets/painting.js +84 -0
- package/office/assets/palm-tree.js +108 -0
- package/office/assets/pc-tower.js +91 -0
- package/office/assets/pendant-light.js +67 -0
- package/office/assets/ping-pong.js +114 -0
- package/office/assets/plant.js +72 -0
- package/office/assets/planter-box.js +95 -0
- package/office/assets/pool-table.js +94 -0
- package/office/assets/printer.js +113 -0
- package/office/assets/reception-desk.js +133 -0
- package/office/assets/rug.js +78 -0
- package/office/assets/sculpture.js +85 -0
- package/office/assets/server-rack.js +98 -0
- package/office/assets/sink.js +109 -0
- package/office/assets/sofa.js +106 -0
- package/office/assets/speaker.js +83 -0
- package/office/assets/spotlight.js +83 -0
- package/office/assets/street-lamp.js +97 -0
- package/office/assets/trash-can.js +83 -0
- package/office/assets/treadmill.js +126 -0
- package/office/assets/trophy.js +89 -0
- package/office/assets/tv-screen.js +79 -0
- package/office/assets/vase.js +84 -0
- package/office/assets/wall-clock.js +84 -0
- package/office/assets/wall.js +53 -0
- package/office/assets/water-cooler.js +146 -0
- package/office/assets/whiteboard.js +115 -0
- package/office/assets.js +3 -431
- package/office/builder.js +791 -355
- package/office/campus-env.js +1012 -1119
- package/office/environment.js +2 -0
- package/office/gallery.js +997 -0
- package/office/index.js +141 -34
- package/office/navigation.js +173 -152
- package/office/player.js +178 -68
- package/office/robot-character.js +272 -0
- package/office/spectator-camera.js +33 -10
- package/office/state.js +2 -0
- package/office/world-save.js +35 -4
- package/package.json +57 -3
- package/providers/comfyui.js +383 -0
- package/providers/dalle.js +79 -0
- package/providers/gemini.js +181 -0
- package/providers/ollama.js +184 -0
- package/providers/replicate.js +115 -0
- package/providers/zai.js +183 -0
- package/runtime-descriptor.js +270 -0
- package/scripts/check-agent-contract-advisory.js +132 -0
- package/scripts/check-api-agent-parity.js +277 -0
- package/scripts/check-autonomy-v2-decision.js +207 -0
- package/scripts/check-autonomy-v2-execution.js +588 -0
- package/scripts/check-autonomy-v2-watchdog.js +224 -0
- package/scripts/check-branch-fork-snapshot.js +337 -0
- package/scripts/check-branch-isolation.js +787 -0
- package/scripts/check-branch-semantics.js +139 -0
- package/scripts/check-dashboard-control-plane.js +1304 -0
- package/scripts/check-docs-onboarding.js +490 -0
- package/scripts/check-event-schema.js +276 -0
- package/scripts/check-evidence-completion.js +239 -0
- package/scripts/check-invariants.js +992 -0
- package/scripts/check-lifecycle-hooks.js +525 -0
- package/scripts/check-managed-team-integration.js +166 -0
- package/scripts/check-markdown-workspace-export.js +548 -0
- package/scripts/check-markdown-workspace-safety.js +347 -0
- package/scripts/check-markdown-workspace.js +136 -0
- package/scripts/check-message-replay.js +429 -0
- package/scripts/check-migration-hardening.js +300 -0
- package/scripts/check-performance-indexing.js +272 -0
- package/scripts/check-provider-capabilities.js +316 -0
- package/scripts/check-runtime-contract.js +109 -0
- package/scripts/check-session-aware-context.js +172 -0
- package/scripts/check-session-lifecycle.js +210 -0
- package/scripts/export-markdown-workspace.js +84 -0
- package/scripts/fixtures/message-replay/clean.jsonl +2 -0
- package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
- package/scripts/migrate-legacy-to-canonical.js +201 -0
- package/scripts/run-verification-suite.js +242 -0
- package/scripts/sync-packaged-docs.js +69 -0
- package/server.js +9577 -7216
- package/state/agents.js +161 -0
- package/state/canonical.js +3068 -0
- package/state/dashboard-queries.js +441 -0
- package/state/evidence.js +56 -0
- package/state/io.js +69 -0
- package/state/markdown-workspace.js +951 -0
- package/state/messages.js +669 -0
- package/state/sessions.js +683 -0
- package/state/tasks-workflows.js +92 -0
- package/templates/debate.json +2 -2
- package/templates/managed.json +4 -4
- package/templates/pair.json +2 -2
- package/templates/review.json +2 -2
- 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 };
|