neoagent 2.3.1-beta.95 → 2.3.1-beta.97
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/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/routes/browser.js +13 -0
- package/server/services/ai/capabilityHealth.js +2 -2
- package/server/services/ai/models.js +4 -4
- package/server/services/ai/providers/openaiCodex.js +316 -17
- package/server/services/ai/settings.js +1 -1
- package/server/services/browser/controller.js +11 -0
- package/server/services/runtime/backends/local-vm.js +3 -0
- package/server/services/social_video/service.js +75 -5
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e299764c07bc83e074bc914963bc6b6a
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "818064985" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -129338,7 +129338,7 @@ r===$&&A.b()
|
|
|
129338
129338
|
o.push(A.ii(p,A.iY(!1,new A.a3(B.tM,A.dT(new A.cI(B.he,new A.a5V(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
|
|
129339
129339
|
if(!s.ay)if(!s.ch){r=s.e
|
|
129340
129340
|
r===$&&A.b()
|
|
129341
|
-
r=B.b.t("
|
|
129341
|
+
r=B.b.t("mp9vo0j5-aadf22e").length!==0&&r.b}if(r){r=s.d
|
|
129342
129342
|
r===$&&A.b()
|
|
129343
129343
|
r=r.ag&&!r.V?84:0
|
|
129344
129344
|
q=s.e
|
|
@@ -134146,7 +134146,7 @@ $S:236}
|
|
|
134146
134146
|
A.Ys.prototype={}
|
|
134147
134147
|
A.Rr.prototype={
|
|
134148
134148
|
mT(a){var s=this
|
|
134149
|
-
if(B.b.t("
|
|
134149
|
+
if(B.b.t("mp9vo0j5-aadf22e").length===0||s.a!=null)return
|
|
134150
134150
|
s.A5()
|
|
134151
134151
|
s.a=A.q1(B.PP,new A.b58(s))},
|
|
134152
134152
|
A5(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
|
|
@@ -134164,7 +134164,7 @@ if(!t.f.b(k)){s=1
|
|
|
134164
134164
|
break}i=J.Z(k,"buildId")
|
|
134165
134165
|
h=i==null?null:B.b.t(J.r(i))
|
|
134166
134166
|
j=h==null?"":h
|
|
134167
|
-
if(J.bm(j)===0||J.d(j,"
|
|
134167
|
+
if(J.bm(j)===0||J.d(j,"mp9vo0j5-aadf22e")){s=1
|
|
134168
134168
|
break}n.b=!0
|
|
134169
134169
|
n.D()
|
|
134170
134170
|
p=2
|
|
@@ -134181,7 +134181,7 @@ case 2:return A.i(o.at(-1),r)}})
|
|
|
134181
134181
|
return A.k($async$A5,r)},
|
|
134182
134182
|
vb(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
|
|
134183
134183
|
var $async$vb=A.h(function(a2,a3){if(a2===1){o.push(a3)
|
|
134184
|
-
s=p}for(;;)switch(s){case 0:if(B.b.t("
|
|
134184
|
+
s=p}for(;;)switch(s){case 0:if(B.b.t("mp9vo0j5-aadf22e").length===0||n.c){s=1
|
|
134185
134185
|
break}n.c=!0
|
|
134186
134186
|
n.D()
|
|
134187
134187
|
p=4
|
package/server/routes/browser.js
CHANGED
|
@@ -66,6 +66,19 @@ router.get('/status', async (req, res) => {
|
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
router.get('/cookies', async (req, res) => {
|
|
70
|
+
try {
|
|
71
|
+
const bc = await getBrowserController(req);
|
|
72
|
+
if (typeof bc.getCookies !== 'function') {
|
|
73
|
+
return res.status(501).json({ error: 'Cookie export is unavailable for this browser provider.' });
|
|
74
|
+
}
|
|
75
|
+
const result = await bc.getCookies();
|
|
76
|
+
res.json(result);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
res.status(500).json({ error: sanitizeError(err) });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
69
82
|
// Launch browser
|
|
70
83
|
router.post('/launch', async (req, res) => {
|
|
71
84
|
try {
|
|
@@ -310,8 +310,8 @@ function getCommandHealth(userId, app, engine) {
|
|
|
310
310
|
configured: Boolean(runtimeManager),
|
|
311
311
|
healthy: Boolean(runtimeManager),
|
|
312
312
|
summary: runtimeManager
|
|
313
|
-
? 'Shell command execution is available
|
|
314
|
-
: 'Shell
|
|
313
|
+
? 'Shell command execution is available.'
|
|
314
|
+
: 'Shell command execution is not available in this environment.',
|
|
315
315
|
});
|
|
316
316
|
}
|
|
317
317
|
|
|
@@ -31,14 +31,14 @@ const STATIC_MODELS = [
|
|
|
31
31
|
purpose: 'coding'
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
id: 'gpt-5.
|
|
35
|
-
label: 'GPT-5.
|
|
34
|
+
id: 'gpt-5.4',
|
|
35
|
+
label: 'GPT-5.4 (Codex Default)',
|
|
36
36
|
provider: 'openai-codex',
|
|
37
37
|
purpose: 'general'
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
id: 'gpt-4
|
|
41
|
-
label: 'GPT-4
|
|
40
|
+
id: 'gpt-5.4-mini',
|
|
41
|
+
label: 'GPT-5.4 mini (Codex Fast)',
|
|
42
42
|
provider: 'openai-codex',
|
|
43
43
|
purpose: 'coding'
|
|
44
44
|
},
|
|
@@ -1,31 +1,330 @@
|
|
|
1
|
-
const
|
|
1
|
+
const OpenAI = require('openai');
|
|
2
|
+
const { BaseProvider } = require('./base');
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
const DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
|
|
5
|
+
|
|
6
|
+
function normalizeContent(content) {
|
|
7
|
+
if (content == null) return '';
|
|
8
|
+
if (typeof content === 'string') return content;
|
|
9
|
+
if (Array.isArray(content)) {
|
|
10
|
+
return content.map((part) => {
|
|
11
|
+
if (!part) return '';
|
|
12
|
+
if (typeof part === 'string') return part;
|
|
13
|
+
if (part.type === 'text' && typeof part.text === 'string') return part.text;
|
|
14
|
+
if (part.type === 'input_text' && typeof part.text === 'string') return part.text;
|
|
15
|
+
return '';
|
|
16
|
+
}).join('');
|
|
17
|
+
}
|
|
18
|
+
return String(content);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeInputContent(content) {
|
|
22
|
+
if (content == null) return [];
|
|
23
|
+
|
|
24
|
+
if (typeof content === 'string') {
|
|
25
|
+
return [{ type: 'input_text', text: content }];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!Array.isArray(content)) {
|
|
29
|
+
const text = String(content);
|
|
30
|
+
return text ? [{ type: 'input_text', text }] : [];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parts = [];
|
|
34
|
+
for (const part of content) {
|
|
35
|
+
if (!part) continue;
|
|
36
|
+
if (typeof part === 'string') {
|
|
37
|
+
if (part.trim()) parts.push({ type: 'input_text', text: part });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (part.type === 'text' && typeof part.text === 'string') {
|
|
41
|
+
parts.push({ type: 'input_text', text: part.text });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (part.type === 'input_text' && typeof part.text === 'string') {
|
|
45
|
+
parts.push({ type: 'input_text', text: part.text });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (part.type === 'image_url') {
|
|
49
|
+
const imageUrl = typeof part.image_url === 'string' ? part.image_url : part.image_url?.url;
|
|
50
|
+
if (imageUrl) {
|
|
51
|
+
parts.push({
|
|
52
|
+
type: 'input_image',
|
|
53
|
+
image_url: imageUrl,
|
|
54
|
+
detail: part.detail || 'auto',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (part.type === 'input_image') {
|
|
60
|
+
parts.push({
|
|
61
|
+
type: 'input_image',
|
|
62
|
+
image_url: part.image_url || null,
|
|
63
|
+
file_id: part.file_id || null,
|
|
64
|
+
detail: part.detail || 'auto',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return parts;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function toFunctionCallOutput(toolCallId, content) {
|
|
73
|
+
return {
|
|
74
|
+
type: 'function_call_output',
|
|
75
|
+
call_id: toolCallId,
|
|
76
|
+
output: normalizeContent(content),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractResponseText(response) {
|
|
81
|
+
if (typeof response?.output_text === 'string' && response.output_text.length > 0) {
|
|
82
|
+
return response.output_text;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parts = [];
|
|
86
|
+
for (const item of response?.output || []) {
|
|
87
|
+
if (item?.type !== 'message') continue;
|
|
88
|
+
for (const content of item.content || []) {
|
|
89
|
+
if (content?.type === 'output_text' && typeof content.text === 'string') {
|
|
90
|
+
parts.push(content.text);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return parts.join('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function extractToolCalls(response) {
|
|
98
|
+
const toolCalls = [];
|
|
99
|
+
for (const item of response?.output || []) {
|
|
100
|
+
if (item?.type !== 'function_call') continue;
|
|
101
|
+
toolCalls.push({
|
|
102
|
+
id: item.call_id || item.id || '',
|
|
103
|
+
type: 'function',
|
|
104
|
+
function: {
|
|
105
|
+
name: item.name || '',
|
|
106
|
+
arguments: item.arguments || '',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return toolCalls.filter((toolCall) => toolCall.id && toolCall.function.name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function formatOpenAIError(err) {
|
|
114
|
+
if (!err || typeof err !== 'object') return 'Unknown OpenAI error';
|
|
115
|
+
const parts = [];
|
|
116
|
+
if (typeof err.status === 'number') parts.push(`HTTP ${err.status}`);
|
|
117
|
+
if (err.type) parts.push(`type=${err.type}`);
|
|
118
|
+
if (err.code) parts.push(`code=${err.code}`);
|
|
119
|
+
if (err.param) parts.push(`param=${err.param}`);
|
|
120
|
+
if (err.request_id) parts.push(`request_id=${err.request_id}`);
|
|
121
|
+
const message = err.message || 'Unknown OpenAI error';
|
|
122
|
+
return parts.length > 0 ? `${message} (${parts.join(', ')})` : message;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
class OpenAICodexProvider extends BaseProvider {
|
|
4
126
|
constructor(config = {}) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
127
|
+
super(config);
|
|
128
|
+
|
|
129
|
+
const baseURL = config.baseUrl || process.env.OPENAI_CODEX_BASE_URL || DEFAULT_BASE_URL;
|
|
130
|
+
|
|
131
|
+
if (!baseURL.includes('chatgpt.com/backend-api/codex') && !baseURL.includes('api.openai.com')) {
|
|
132
|
+
console.warn(`[OpenAICodex] Using non-official base URL: ${baseURL}`);
|
|
133
|
+
} else if (baseURL.includes('chatgpt.com/backend-api/codex')) {
|
|
134
|
+
console.info(`[OpenAICodex] Using ChatGPT Codex endpoint: ${baseURL}`);
|
|
12
135
|
}
|
|
13
136
|
|
|
14
|
-
|
|
15
|
-
|
|
137
|
+
this.name = 'openai-codex';
|
|
138
|
+
this.models = [
|
|
139
|
+
'gpt-5.4',
|
|
140
|
+
'gpt-5.4-mini',
|
|
141
|
+
];
|
|
142
|
+
this.reasoningModels = new Set([
|
|
143
|
+
'gpt-5.4',
|
|
144
|
+
'gpt-5.4-mini',
|
|
145
|
+
'gpt-5.4-nano',
|
|
146
|
+
]);
|
|
147
|
+
this.client = new OpenAI({
|
|
16
148
|
apiKey: config.apiKey || process.env.OPENAI_CODEX_ACCESS_TOKEN,
|
|
17
|
-
|
|
149
|
+
baseURL,
|
|
18
150
|
defaultHeaders: {
|
|
19
151
|
'Editor-Version': process.env.OPENAI_CODEX_EDITOR_VERSION || 'vscode/1.99.0',
|
|
20
152
|
'Editor-Plugin-Version': process.env.OPENAI_CODEX_EDITOR_PLUGIN_VERSION || 'neoagent/1.0.0',
|
|
21
|
-
'User-Agent': process.env.OPENAI_CODEX_USER_AGENT || 'NeoAgent/1.0.0'
|
|
22
|
-
}
|
|
153
|
+
'User-Agent': process.env.OPENAI_CODEX_USER_AGENT || 'NeoAgent/1.0.0',
|
|
154
|
+
},
|
|
23
155
|
});
|
|
24
|
-
this.name = 'openai-codex';
|
|
25
156
|
}
|
|
26
157
|
|
|
27
|
-
|
|
28
|
-
|
|
158
|
+
_isReasoningModel(model) {
|
|
159
|
+
if (!model) return false;
|
|
160
|
+
for (const id of this.reasoningModels) {
|
|
161
|
+
if (model === id || model.startsWith(`${id}-`)) return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_buildRequest(messages = [], tools = [], options = {}, model = '') {
|
|
167
|
+
const instructions = [];
|
|
168
|
+
const input = [];
|
|
169
|
+
|
|
170
|
+
for (const msg of messages || []) {
|
|
171
|
+
if (!msg || !msg.role) continue;
|
|
172
|
+
|
|
173
|
+
if ((msg.role === 'system' || msg.role === 'developer') && msg.content != null) {
|
|
174
|
+
const text = normalizeContent(msg.content).trim();
|
|
175
|
+
if (text) instructions.push(text);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (msg.role === 'tool') {
|
|
180
|
+
const toolCallId = String(msg.tool_call_id || '').trim();
|
|
181
|
+
if (!toolCallId) continue;
|
|
182
|
+
input.push(toFunctionCallOutput(toolCallId, msg.content));
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const content = normalizeInputContent(msg.content);
|
|
187
|
+
if (content.length > 0) {
|
|
188
|
+
input.push({
|
|
189
|
+
type: 'message',
|
|
190
|
+
role: msg.role === 'assistant' ? 'assistant' : 'user',
|
|
191
|
+
content,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (msg.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
196
|
+
for (const toolCall of msg.tool_calls) {
|
|
197
|
+
const name = String(toolCall?.function?.name || '').trim();
|
|
198
|
+
const argumentsText = String(toolCall?.function?.arguments || '');
|
|
199
|
+
const callId = String(toolCall?.id || toolCall?.call_id || '').trim();
|
|
200
|
+
if (!name || !callId) continue;
|
|
201
|
+
input.push({
|
|
202
|
+
type: 'function_call',
|
|
203
|
+
id: callId,
|
|
204
|
+
call_id: callId,
|
|
205
|
+
name,
|
|
206
|
+
arguments: argumentsText,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const request = {
|
|
213
|
+
input,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
if (instructions.length > 0) {
|
|
217
|
+
request.instructions = instructions.join('\n\n');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (tools && tools.length > 0) {
|
|
221
|
+
request.tools = this.formatTools(tools);
|
|
222
|
+
request.tool_choice = options.toolChoice || 'auto';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
request.max_output_tokens = options.maxTokens || 16384;
|
|
226
|
+
|
|
227
|
+
if (options.temperature !== undefined && options.temperature !== null) {
|
|
228
|
+
request.temperature = options.temperature;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const reasoningEffort = options.reasoningEffort || options.reasoning_effort;
|
|
232
|
+
if (reasoningEffort || this._isReasoningModel(model)) {
|
|
233
|
+
request.reasoning = {
|
|
234
|
+
effort: reasoningEffort || 'medium',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return request;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async chat(messages, tools = [], options = {}) {
|
|
242
|
+
const model = options.model || this.config.model || this.getDefaultModel();
|
|
243
|
+
const request = this._buildRequest(messages, tools, options, model);
|
|
244
|
+
let response;
|
|
245
|
+
try {
|
|
246
|
+
response = await this.client.responses.create({
|
|
247
|
+
model,
|
|
248
|
+
...request,
|
|
249
|
+
});
|
|
250
|
+
} catch (err) {
|
|
251
|
+
throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const toolCalls = extractToolCalls(response);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
content: extractResponseText(response),
|
|
258
|
+
toolCalls,
|
|
259
|
+
finishReason: toolCalls.length > 0 ? 'tool_calls' : 'stop',
|
|
260
|
+
usage: response.usage ? {
|
|
261
|
+
promptTokens: response.usage.input_tokens,
|
|
262
|
+
completionTokens: response.usage.output_tokens,
|
|
263
|
+
totalTokens: response.usage.total_tokens,
|
|
264
|
+
} : null,
|
|
265
|
+
model: response.model,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async *stream(messages, tools = [], options = {}) {
|
|
270
|
+
const model = options.model || this.config.model || this.getDefaultModel();
|
|
271
|
+
const request = this._buildRequest(messages, tools, options, model);
|
|
272
|
+
let stream;
|
|
273
|
+
try {
|
|
274
|
+
stream = await this.client.responses.create({
|
|
275
|
+
model,
|
|
276
|
+
...request,
|
|
277
|
+
stream: true,
|
|
278
|
+
});
|
|
279
|
+
} catch (err) {
|
|
280
|
+
throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let content = '';
|
|
284
|
+
let finalResponse = null;
|
|
285
|
+
|
|
286
|
+
for await (const event of stream) {
|
|
287
|
+
if (event.type === 'response.output_text.delta' && typeof event.delta === 'string') {
|
|
288
|
+
content += event.delta;
|
|
289
|
+
yield { type: 'content', content: event.delta };
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (event.type === 'response.completed') {
|
|
294
|
+
finalResponse = event.response;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const response = finalResponse || {};
|
|
299
|
+
const toolCalls = extractToolCalls(response);
|
|
300
|
+
const finalContent = extractResponseText(response) || content;
|
|
301
|
+
|
|
302
|
+
if (toolCalls.length > 0) {
|
|
303
|
+
yield {
|
|
304
|
+
type: 'tool_calls',
|
|
305
|
+
content: finalContent,
|
|
306
|
+
toolCalls,
|
|
307
|
+
usage: response.usage ? {
|
|
308
|
+
promptTokens: response.usage.input_tokens,
|
|
309
|
+
completionTokens: response.usage.output_tokens,
|
|
310
|
+
totalTokens: response.usage.total_tokens,
|
|
311
|
+
} : null,
|
|
312
|
+
};
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
yield {
|
|
317
|
+
type: 'done',
|
|
318
|
+
content: finalContent,
|
|
319
|
+
toolCalls: [],
|
|
320
|
+
finishReason: 'stop',
|
|
321
|
+
usage: response.usage ? {
|
|
322
|
+
promptTokens: response.usage.input_tokens,
|
|
323
|
+
completionTokens: response.usage.output_tokens,
|
|
324
|
+
totalTokens: response.usage.total_tokens,
|
|
325
|
+
} : null,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
29
328
|
}
|
|
30
329
|
|
|
31
330
|
module.exports = { OpenAICodexProvider };
|
|
@@ -77,7 +77,7 @@ const AI_PROVIDER_DEFINITIONS = Object.freeze({
|
|
|
77
77
|
supportsApiKey: true,
|
|
78
78
|
supportsBaseUrl: true,
|
|
79
79
|
defaultEnabled: false,
|
|
80
|
-
defaultBaseUrl: 'https://
|
|
80
|
+
defaultBaseUrl: 'https://chatgpt.com/backend-api/codex'
|
|
81
81
|
},
|
|
82
82
|
ollama: {
|
|
83
83
|
id: 'ollama',
|
|
@@ -779,6 +779,17 @@ class BrowserController {
|
|
|
779
779
|
};
|
|
780
780
|
}
|
|
781
781
|
|
|
782
|
+
async getCookies() {
|
|
783
|
+
await this.ensureBrowser();
|
|
784
|
+
if (!this.context || typeof this.context.cookies !== 'function') {
|
|
785
|
+
return { cookies: [] };
|
|
786
|
+
}
|
|
787
|
+
const cookies = await this.context.cookies().catch(() => []);
|
|
788
|
+
return {
|
|
789
|
+
cookies: Array.isArray(cookies) ? cookies : [],
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
782
793
|
async close() {
|
|
783
794
|
if (this.page && !this.page.isClosed()) {
|
|
784
795
|
await this.page.close().catch(() => { });
|
|
@@ -272,6 +272,9 @@ class VmBrowserProvider {
|
|
|
272
272
|
const status = await this.client.request('GET', '/browser/status');
|
|
273
273
|
return Number(status?.pages || 0);
|
|
274
274
|
}
|
|
275
|
+
async getCookies() {
|
|
276
|
+
return this.client.request('GET', '/browser/cookies');
|
|
277
|
+
}
|
|
275
278
|
async setHeadless(value) {
|
|
276
279
|
this.headless = true;
|
|
277
280
|
return { success: true };
|
|
@@ -126,6 +126,35 @@ function resolveVoiceSttConfigFromSettings(settings = {}) {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
function serializeCookiesForNetscapeJar(cookies = []) {
|
|
130
|
+
const lines = ['# Netscape HTTP Cookie File'];
|
|
131
|
+
for (const cookie of Array.isArray(cookies) ? cookies : []) {
|
|
132
|
+
if (!cookie || typeof cookie !== 'object') continue;
|
|
133
|
+
const domain = String(cookie.domain || '').trim();
|
|
134
|
+
const name = String(cookie.name || '').trim();
|
|
135
|
+
const value = String(cookie.value || '').replace(/[\r\n\t]/g, ' ');
|
|
136
|
+
if (!domain || !name) continue;
|
|
137
|
+
const cookieDomain = domain.startsWith('.') ? domain : domain;
|
|
138
|
+
const includeSubdomains = domain.startsWith('.') ? 'TRUE' : 'FALSE';
|
|
139
|
+
const pathValue = String(cookie.path || '/').trim() || '/';
|
|
140
|
+
const secure = cookie.secure ? 'TRUE' : 'FALSE';
|
|
141
|
+
const expires = Number.isFinite(Number(cookie.expires)) && Number(cookie.expires) > 0
|
|
142
|
+
? String(Math.floor(Number(cookie.expires)))
|
|
143
|
+
: '0';
|
|
144
|
+
const httpOnlyPrefix = cookie.httpOnly ? '#HttpOnly_' : '';
|
|
145
|
+
lines.push([
|
|
146
|
+
`${httpOnlyPrefix}${cookieDomain}`,
|
|
147
|
+
includeSubdomains,
|
|
148
|
+
pathValue,
|
|
149
|
+
secure,
|
|
150
|
+
expires,
|
|
151
|
+
name,
|
|
152
|
+
value,
|
|
153
|
+
].join('\t'));
|
|
154
|
+
}
|
|
155
|
+
return `${lines.join('\n')}\n`;
|
|
156
|
+
}
|
|
157
|
+
|
|
129
158
|
function fileExists(filePath) {
|
|
130
159
|
try {
|
|
131
160
|
return fs.statSync(filePath).isFile();
|
|
@@ -240,8 +269,14 @@ class SocialVideoService {
|
|
|
240
269
|
|
|
241
270
|
const pageMetadata = await this.#resolvePageMetadata(userId, normalizedUrl, warnings);
|
|
242
271
|
jobDir = await fsp.mkdtemp(path.join(SOCIAL_VIDEO_TMP_DIR, `${platform}-${Date.now()}-`));
|
|
272
|
+
const cookieFilePath = await this.#resolveCookieFile({
|
|
273
|
+
userId,
|
|
274
|
+
platform,
|
|
275
|
+
jobDir,
|
|
276
|
+
warnings,
|
|
277
|
+
});
|
|
243
278
|
|
|
244
|
-
const mediaInfo = await this.#readMediaInfo(normalizedUrl, jobDir);
|
|
279
|
+
const mediaInfo = await this.#readMediaInfo(normalizedUrl, jobDir, cookieFilePath);
|
|
245
280
|
const baseTitle = String(pageMetadata.title || mediaInfo.title || '').trim();
|
|
246
281
|
const baseDescription = String(pageMetadata.description || mediaInfo.description || '').trim();
|
|
247
282
|
const resolvedUrl = String(pageMetadata.resolvedUrl || mediaInfo.webpage_url || normalizedUrl).trim();
|
|
@@ -264,6 +299,7 @@ class SocialVideoService {
|
|
|
264
299
|
captionTrack,
|
|
265
300
|
transcriptDecision,
|
|
266
301
|
jobDir,
|
|
302
|
+
cookieFilePath,
|
|
267
303
|
userId,
|
|
268
304
|
agentId,
|
|
269
305
|
warnings,
|
|
@@ -276,6 +312,7 @@ class SocialVideoService {
|
|
|
276
312
|
sourceUrl: normalizedUrl,
|
|
277
313
|
mediaInfo,
|
|
278
314
|
jobDir,
|
|
315
|
+
cookieFilePath,
|
|
279
316
|
warnings,
|
|
280
317
|
});
|
|
281
318
|
|
|
@@ -432,10 +469,11 @@ class SocialVideoService {
|
|
|
432
469
|
};
|
|
433
470
|
}
|
|
434
471
|
|
|
435
|
-
async #readMediaInfo(normalizedUrl, jobDir) {
|
|
472
|
+
async #readMediaInfo(normalizedUrl, jobDir, cookieFilePath = null) {
|
|
436
473
|
const infoTemplate = path.join(jobDir, 'media.%(ext)s');
|
|
437
474
|
const infoPath = path.join(jobDir, 'media.info.json');
|
|
438
|
-
const
|
|
475
|
+
const cookieArg = cookieFilePath ? ` --cookies ${shellEscape(cookieFilePath)}` : '';
|
|
476
|
+
const command = `${shellEscape(this.ytDlpBin)} --quiet --no-warnings --no-playlist --skip-download --write-info-json --no-clean-infojson${cookieArg} -o ${shellEscape(infoTemplate)} -- ${shellEscape(normalizedUrl)}`;
|
|
439
477
|
await this.#runCommand(command, { cwd: jobDir, timeout: 4 * 60 * 1000 });
|
|
440
478
|
if (!fileExists(infoPath)) {
|
|
441
479
|
throw new Error('yt-dlp did not produce an info JSON artifact.');
|
|
@@ -486,7 +524,8 @@ class SocialVideoService {
|
|
|
486
524
|
|
|
487
525
|
async #transcribeViaStt(context) {
|
|
488
526
|
const template = path.join(context.jobDir, 'audio.%(ext)s');
|
|
489
|
-
const
|
|
527
|
+
const cookieArg = context.cookieFilePath ? ` --cookies ${shellEscape(context.cookieFilePath)}` : '';
|
|
528
|
+
const command = `${shellEscape(this.ytDlpBin)} --quiet --no-warnings --no-playlist${cookieArg} -o ${shellEscape(template)} -f bestaudio -- ${shellEscape(context.sourceUrl)}`;
|
|
490
529
|
await this.#runCommand(command, { cwd: context.jobDir, timeout: 10 * 60 * 1000 });
|
|
491
530
|
|
|
492
531
|
const audioPath = firstFileMatching(context.jobDir, 'audio.');
|
|
@@ -521,6 +560,36 @@ class SocialVideoService {
|
|
|
521
560
|
});
|
|
522
561
|
}
|
|
523
562
|
|
|
563
|
+
async #resolveCookieFile(context) {
|
|
564
|
+
if (context.platform !== 'instagram') {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
if (!this.runtimeManager || typeof this.runtimeManager.getBrowserProviderForUser !== 'function') {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const browser = await Promise.resolve(
|
|
572
|
+
this.runtimeManager.getBrowserProviderForUser(context.userId),
|
|
573
|
+
).catch(() => null);
|
|
574
|
+
if (!browser || typeof browser.getCookies !== 'function') {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const payload = await browser.getCookies().catch((error) => {
|
|
579
|
+
context.warnings.push(`Browser cookie export failed: ${error.message}`);
|
|
580
|
+
return null;
|
|
581
|
+
});
|
|
582
|
+
const cookies = Array.isArray(payload?.cookies) ? payload.cookies : [];
|
|
583
|
+
if (cookies.length === 0) {
|
|
584
|
+
context.warnings.push('Browser cookie export returned no cookies for Instagram.');
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const cookieFilePath = path.join(context.jobDir, 'browser.cookies.txt');
|
|
589
|
+
await fsp.writeFile(cookieFilePath, serializeCookiesForNetscapeJar(cookies), 'utf8');
|
|
590
|
+
return cookieFilePath;
|
|
591
|
+
}
|
|
592
|
+
|
|
524
593
|
async #resolveFrameImage(context) {
|
|
525
594
|
const downloadedFrame = await this.#extractFrameFromVideo(context).catch((error) => {
|
|
526
595
|
context.warnings.push(`Frame extraction failed: ${error.message}`);
|
|
@@ -540,7 +609,8 @@ class SocialVideoService {
|
|
|
540
609
|
|
|
541
610
|
async #extractFrameFromVideo(context) {
|
|
542
611
|
const template = path.join(context.jobDir, 'video.%(ext)s');
|
|
543
|
-
const
|
|
612
|
+
const cookieArg = context.cookieFilePath ? ` --cookies ${shellEscape(context.cookieFilePath)}` : '';
|
|
613
|
+
const downloadCommand = `${shellEscape(this.ytDlpBin)} --quiet --no-warnings --no-playlist${cookieArg} -o ${shellEscape(template)} -f "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/best" --merge-output-format mp4 -- ${shellEscape(context.sourceUrl)}`;
|
|
544
614
|
await this.#runCommand(downloadCommand, { cwd: context.jobDir, timeout: 14 * 60 * 1000 });
|
|
545
615
|
|
|
546
616
|
const videoPath = firstFileMatching(context.jobDir, 'video.');
|