@paytaca/opencode-plugin 0.1.16 → 0.2.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/README.md +67 -0
- package/dist/bundled/mcp.d.ts +2 -0
- package/dist/bundled/mcp.d.ts.map +1 -0
- package/dist/bundled/mcp.js +615 -0
- package/dist/bundled/mcp.js.map +1 -0
- package/dist/bundled/proxy.d.ts +1 -1
- package/dist/bundled/proxy.d.ts.map +1 -1
- package/dist/bundled/proxy.js +132 -21
- package/dist/bundled/proxy.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -2
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +4 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +99 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +1 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +1 -0
- package/dist/proxy.js.map +1 -1
- package/dist/selfheal.d.ts +13 -0
- package/dist/selfheal.d.ts.map +1 -0
- package/dist/selfheal.js +229 -0
- package/dist/selfheal.js.map +1 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +148 -9
package/dist/bundled/proxy.js
CHANGED
|
@@ -50,6 +50,17 @@ function log(message) {
|
|
|
50
50
|
// step: 'tier_select' (user must pick a tier) or 'approval' (yes/no)
|
|
51
51
|
const pendingPayments = new Map();
|
|
52
52
|
|
|
53
|
+
// Track the last model used per wallet so we can detect model switches and
|
|
54
|
+
// make sure a switched-to model never hits a stale payment prompt.
|
|
55
|
+
const lastModelPerWallet = new Map();
|
|
56
|
+
|
|
57
|
+
// Monotonic id per incoming request. A response may only clear the pending
|
|
58
|
+
// payment created by its own request — concurrent requests from opencode share
|
|
59
|
+
// the wallet hash, and a plain 200 finishing mid-payment must not clobber the
|
|
60
|
+
// pending entry another request just created (that made tier selections
|
|
61
|
+
// "2"/"3" fall through to a fresh 402 and re-show the prompt forever).
|
|
62
|
+
let requestCounter = 0;
|
|
63
|
+
|
|
53
64
|
// Utility: run shell command and return output
|
|
54
65
|
function runCommand(cmd, args = []) {
|
|
55
66
|
return new Promise((resolve, reject) => {
|
|
@@ -141,10 +152,17 @@ function sseDone(res) {
|
|
|
141
152
|
res.write('data: [DONE]\\n\\n');
|
|
142
153
|
}
|
|
143
154
|
|
|
155
|
+
// Zero-width marker prepended to every synthetic proxy message (tier
|
|
156
|
+
// prompts, credits/plans output, payment notices). The opencode plugin
|
|
157
|
+
// strips marker-carrying assistant messages from LLM context — proxy chatter
|
|
158
|
+
// is not relevant to the coding session — while the user still sees them in
|
|
159
|
+
// the UI (zero-width characters don't render).
|
|
160
|
+
const PROXY_MARKER = String.fromCharCode(0x200b, 0x200b, 0x200b, 0x200b);
|
|
161
|
+
|
|
144
162
|
// Stream the tier-selection prompt body (SSE lines) into an in-progress response.
|
|
145
163
|
// When includeRole is false the leading role delta is skipped, so the body can be
|
|
146
164
|
// appended to a stream that already emitted content (e.g. after a payment failure).
|
|
147
|
-
async function streamTierSelectionBody(res, walletHash, modelName, tiers, includeRole) {
|
|
165
|
+
async function streamTierSelectionBody(res, walletHash, modelName, tiers, includeRole, otherModels) {
|
|
148
166
|
if (includeRole !== false) {
|
|
149
167
|
sseLine(res, {
|
|
150
168
|
id: 'tier-1',
|
|
@@ -159,7 +177,7 @@ async function streamTierSelectionBody(res, walletHash, modelName, tiers, includ
|
|
|
159
177
|
sseLine(res, {
|
|
160
178
|
id: 'tier-2',
|
|
161
179
|
object: 'chat.completion.chunk',
|
|
162
|
-
choices: [{ index: 0, delta: { content: '⏳ Initializing Paytaca AI provider...\\n' }, finish_reason: null }],
|
|
180
|
+
choices: [{ index: 0, delta: { content: PROXY_MARKER + '⏳ Initializing Paytaca AI provider...\\n' }, finish_reason: null }],
|
|
163
181
|
});
|
|
164
182
|
|
|
165
183
|
const hasCli = await checkPaytacaCli();
|
|
@@ -218,6 +236,16 @@ async function streamTierSelectionBody(res, walletHash, modelName, tiers, includ
|
|
|
218
236
|
choices: [{ index: 0, delta: { content: '💳 Select a plan for **' + (modelName || 'AI Model') + '**\\n\\n' }, finish_reason: null }],
|
|
219
237
|
});
|
|
220
238
|
|
|
239
|
+
// If other models still have paid credits, tell the user they can switch
|
|
240
|
+
// instead of buying a new plan (only when there is something to suggest).
|
|
241
|
+
if (otherModels && otherModels.length > 0) {
|
|
242
|
+
sseLine(res, {
|
|
243
|
+
id: 'tier-9b',
|
|
244
|
+
object: 'chat.completion.chunk',
|
|
245
|
+
choices: [{ index: 0, delta: { content: otherModelsHint(otherModels) }, finish_reason: null }],
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
221
249
|
// Build all tier lines into one string so backtick markdown renders
|
|
222
250
|
// consistently (same as the 'plans' command).
|
|
223
251
|
let tiersContent = '';
|
|
@@ -252,7 +280,7 @@ async function streamTierSelectionBody(res, walletHash, modelName, tiers, includ
|
|
|
252
280
|
}
|
|
253
281
|
|
|
254
282
|
// Build and stream a full tier-selection prompt (headers + body + [DONE]) to the client.
|
|
255
|
-
async function streamTierSelectionPrompt(res, walletHash, modelName, tiers) {
|
|
283
|
+
async function streamTierSelectionPrompt(res, walletHash, modelName, tiers, otherModels) {
|
|
256
284
|
if (!res.headersSent) {
|
|
257
285
|
res.writeHead(200, {
|
|
258
286
|
'Content-Type': 'text/event-stream',
|
|
@@ -261,7 +289,7 @@ async function streamTierSelectionPrompt(res, walletHash, modelName, tiers) {
|
|
|
261
289
|
'Connection': 'keep-alive',
|
|
262
290
|
});
|
|
263
291
|
}
|
|
264
|
-
await streamTierSelectionBody(res, walletHash, modelName, tiers, true);
|
|
292
|
+
await streamTierSelectionBody(res, walletHash, modelName, tiers, true, otherModels);
|
|
265
293
|
sseDone(res);
|
|
266
294
|
res.end();
|
|
267
295
|
}
|
|
@@ -269,7 +297,7 @@ async function streamTierSelectionPrompt(res, walletHash, modelName, tiers) {
|
|
|
269
297
|
// Build and stream SSE loading sequence + payment prompt
|
|
270
298
|
// Stream SSE notice when the upstream (OpenRouter) account lacks balance to fund
|
|
271
299
|
// the request. Replaces the old single-tier yes/no approval prompt.
|
|
272
|
-
async function streamLowBalanceNotice(res, modelName) {
|
|
300
|
+
async function streamLowBalanceNotice(res, modelName, otherModels) {
|
|
273
301
|
res.writeHead(200, {
|
|
274
302
|
'Content-Type': 'text/event-stream',
|
|
275
303
|
'Cache-Control': 'no-cache',
|
|
@@ -285,10 +313,13 @@ async function streamLowBalanceNotice(res, modelName) {
|
|
|
285
313
|
choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }],
|
|
286
314
|
});
|
|
287
315
|
|
|
316
|
+
// Include the other-models hint (when available) so the user knows they can
|
|
317
|
+
// switch to a model that still has credits instead of being stuck.
|
|
318
|
+
const hint = otherModelsHint(otherModels);
|
|
288
319
|
sseLine(res, {
|
|
289
320
|
id: 'lb-2',
|
|
290
321
|
object: 'chat.completion.chunk',
|
|
291
|
-
choices: [{ index: 0, delta: { content: '⚠️ OpenRouter balance is low — please top up before continuing.\\n' }, finish_reason: 'stop' }],
|
|
322
|
+
choices: [{ index: 0, delta: { content: PROXY_MARKER + '⚠️ OpenRouter balance is low — please top up before continuing.\\n' + hint }, finish_reason: 'stop' }],
|
|
292
323
|
});
|
|
293
324
|
|
|
294
325
|
sseLine(res, {
|
|
@@ -680,7 +711,7 @@ async function streamPaymentFailureAndRetry(res, walletHash, pendingPayload, mes
|
|
|
680
711
|
object: 'chat.completion.chunk',
|
|
681
712
|
created: Math.floor(Date.now() / 1000),
|
|
682
713
|
model: 'deepseek/deepseek-v4-flash',
|
|
683
|
-
choices: [{ index: 0, delta: { content: message }, finish_reason: 'stop' }],
|
|
714
|
+
choices: [{ index: 0, delta: { content: PROXY_MARKER + message }, finish_reason: 'stop' }],
|
|
684
715
|
});
|
|
685
716
|
} catch (e) {
|
|
686
717
|
}
|
|
@@ -815,6 +846,58 @@ function getLastUserMessageContent(body) {
|
|
|
815
846
|
}
|
|
816
847
|
}
|
|
817
848
|
|
|
849
|
+
// Fetch wallet status and return other models that still have remaining time
|
|
850
|
+
// credits, excluding the model currently being requested. Returns an array of
|
|
851
|
+
// { modelId, displayName, remainingSeconds } or [] when nothing qualifies
|
|
852
|
+
// (or the status endpoint is unreachable). This powers the "you can switch to
|
|
853
|
+
// another model" hint on 402 responses.
|
|
854
|
+
async function getOtherModelsWithCredits(walletHash, excludeModelId) {
|
|
855
|
+
try {
|
|
856
|
+
const statusRes = await fetch(BACKEND_URL + '/v1/wallet/status', {
|
|
857
|
+
headers: { 'X-Wallet-Hash': walletHash }
|
|
858
|
+
});
|
|
859
|
+
if (!statusRes.ok) {
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
const statusData = await statusRes.json();
|
|
863
|
+
const sessions = Array.isArray(statusData.sessions) ? statusData.sessions : [];
|
|
864
|
+
const others = [];
|
|
865
|
+
for (const s of sessions) {
|
|
866
|
+
const modelId = s.ai_model || s.model_id || '';
|
|
867
|
+
if (excludeModelId && modelId && modelId === excludeModelId) {
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
const remaining = Number(s.time_remaining_seconds) || 0;
|
|
871
|
+
if (remaining > 0) {
|
|
872
|
+
others.push({
|
|
873
|
+
modelId: modelId,
|
|
874
|
+
displayName: s.display_name || modelId || 'Unknown model',
|
|
875
|
+
remainingSeconds: remaining,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return others;
|
|
880
|
+
} catch (err) {
|
|
881
|
+
log('Failed to check other models with credits: ' + err.message);
|
|
882
|
+
return [];
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Build a hint listing other models that still have remaining credits, so the
|
|
887
|
+
// user knows they can switch instead of buying a new plan. Returns '' when
|
|
888
|
+
// there is nothing worth suggesting.
|
|
889
|
+
function otherModelsHint(otherModels) {
|
|
890
|
+
if (!otherModels || otherModels.length === 0) {
|
|
891
|
+
return '';
|
|
892
|
+
}
|
|
893
|
+
let hint = '\\n💡 You have remaining credits on other models:\\n';
|
|
894
|
+
for (const m of otherModels) {
|
|
895
|
+
hint += ' - **' + m.displayName + '** — ' + formatDuration(m.remainingSeconds) + ' remaining\\n';
|
|
896
|
+
}
|
|
897
|
+
hint += 'Switch to one of these models to keep chatting without a new purchase.\\n\\n';
|
|
898
|
+
return hint;
|
|
899
|
+
}
|
|
900
|
+
|
|
818
901
|
async function handleTimeCreditsCommand(res, walletHash) {
|
|
819
902
|
log('Time command for wallet ' + walletHash?.substring(0, 16) + '...');
|
|
820
903
|
const statusUrl = BACKEND_URL + '/v1/wallet/status';
|
|
@@ -859,7 +942,7 @@ async function handleTimeCreditsCommand(res, walletHash) {
|
|
|
859
942
|
sseLine(res, {
|
|
860
943
|
id: 'time-2',
|
|
861
944
|
object: 'chat.completion.chunk',
|
|
862
|
-
choices: [{ index: 0, delta: { content: content + '\\n' }, finish_reason: 'stop' }],
|
|
945
|
+
choices: [{ index: 0, delta: { content: PROXY_MARKER + content + '\\n' }, finish_reason: 'stop' }],
|
|
863
946
|
});
|
|
864
947
|
sseLine(res, {
|
|
865
948
|
id: 'time-3',
|
|
@@ -941,7 +1024,7 @@ async function handlePricingCommand(res) {
|
|
|
941
1024
|
sseLine(res, {
|
|
942
1025
|
id: 'price-2',
|
|
943
1026
|
object: 'chat.completion.chunk',
|
|
944
|
-
choices: [{ index: 0, delta: { content: content + '\\n' }, finish_reason: 'stop' }],
|
|
1027
|
+
choices: [{ index: 0, delta: { content: PROXY_MARKER + content + '\\n' }, finish_reason: 'stop' }],
|
|
945
1028
|
});
|
|
946
1029
|
sseLine(res, {
|
|
947
1030
|
id: 'price-3',
|
|
@@ -1014,6 +1097,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1014
1097
|
req.on('end', async () => {
|
|
1015
1098
|
try {
|
|
1016
1099
|
const walletHash = req.headers['x-wallet-hash'];
|
|
1100
|
+
const proxyReqId = ++requestCounter;
|
|
1017
1101
|
const lastContent = getLastUserMessageContent(body);
|
|
1018
1102
|
|
|
1019
1103
|
log('Request received: wallet=' + (walletHash?.substring(0, 16) || 'none') + '..., bodyLen=' + body.length + ', pending=' + pendingPayments.has(walletHash));
|
|
@@ -1040,19 +1124,35 @@ const server = http.createServer(async (req, res) => {
|
|
|
1040
1124
|
|
|
1041
1125
|
// Check if there's a pending payment for this wallet
|
|
1042
1126
|
var pendingPayload = pendingPayments.get(walletHash);
|
|
1127
|
+
|
|
1128
|
+
// Parse the model requested by this call — used for switch detection
|
|
1129
|
+
// and for clearing stale pending payments tied to a previous model.
|
|
1130
|
+
var reqModel = '';
|
|
1131
|
+
try { reqModel = JSON.parse(body).model || ''; } catch (e) {}
|
|
1043
1132
|
|
|
1044
1133
|
// If there's a pending payment for a different model, clear it so the
|
|
1045
1134
|
// new request can be forwarded fresh to Django. This prevents the
|
|
1046
1135
|
// proxy from re-showing a stale payment prompt when the user switches
|
|
1047
1136
|
// to a different model mid-conversation.
|
|
1048
1137
|
if (pendingPayload) {
|
|
1049
|
-
var reqModel = '';
|
|
1050
|
-
try { reqModel = JSON.parse(body).model || ''; } catch (e) {}
|
|
1051
1138
|
if (reqModel && pendingPayload.modelId && reqModel !== pendingPayload.modelId) {
|
|
1052
1139
|
pendingPayments.delete(walletHash);
|
|
1053
1140
|
pendingPayload = null;
|
|
1054
1141
|
}
|
|
1055
1142
|
}
|
|
1143
|
+
|
|
1144
|
+
// Model-switch detection: remember which model this wallet last used.
|
|
1145
|
+
// When a switch is detected, log it — opencode carries the full
|
|
1146
|
+
// conversation history on the next message, so the last prompt is
|
|
1147
|
+
// effectively re-sent to the new model. If that model has no credits,
|
|
1148
|
+
// the standard 402 flow shows the buy-plan prompt for it.
|
|
1149
|
+
const prevModel = lastModelPerWallet.get(walletHash) || '';
|
|
1150
|
+
if (reqModel && prevModel && reqModel !== prevModel) {
|
|
1151
|
+
log('Model switch detected for wallet ' + (walletHash?.substring(0, 16) || 'none') + ': ' + prevModel + ' -> ' + reqModel);
|
|
1152
|
+
}
|
|
1153
|
+
if (reqModel) {
|
|
1154
|
+
lastModelPerWallet.set(walletHash, reqModel);
|
|
1155
|
+
}
|
|
1056
1156
|
|
|
1057
1157
|
if (pendingPayload) {
|
|
1058
1158
|
// Check for tier selection first
|
|
@@ -1110,7 +1210,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1110
1210
|
sseLine(res, {
|
|
1111
1211
|
id: 'balance-err',
|
|
1112
1212
|
object: 'chat.completion.chunk',
|
|
1113
|
-
choices: [{ index: 0, delta: { content: '\\n\\n❌ **Insufficient balance** — You have **' + (currentBalanceSats / 100000000).toFixed(8) + ' BCH** but need **' + (selectedTier.price_sats / 100000000).toFixed(8) + ' BCH** for this plan. Top up at least **' + neededBch.toFixed(8) + ' BCH** more.' + neededLine + '\\n\\nType \\\`balance\\\` to re-check or try a different plan:' }, finish_reason: 'stop' }],
|
|
1213
|
+
choices: [{ index: 0, delta: { content: PROXY_MARKER + '\\n\\n❌ **Insufficient balance** — You have **' + (currentBalanceSats / 100000000).toFixed(8) + ' BCH** but need **' + (selectedTier.price_sats / 100000000).toFixed(8) + ' BCH** for this plan. Top up at least **' + neededBch.toFixed(8) + ' BCH** more.' + neededLine + '\\n\\nType \\\`balance\\\` to re-check or try a different plan:' }, finish_reason: 'stop' }],
|
|
1114
1214
|
});
|
|
1115
1215
|
sseLine(res, {
|
|
1116
1216
|
id: 'balance-err-done',
|
|
@@ -1164,7 +1264,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1164
1264
|
if (res.headersSent && !res.destroyed && !res.writableEnded) {
|
|
1165
1265
|
if (isTimeout) {
|
|
1166
1266
|
try {
|
|
1167
|
-
sseLine(res, { id: 'pay-err', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'deepseek/deepseek-v4-flash', choices: [{ index: 0, delta: { content: sseContent }, finish_reason: 'stop' }] });
|
|
1267
|
+
sseLine(res, { id: 'pay-err', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'deepseek/deepseek-v4-flash', choices: [{ index: 0, delta: { content: PROXY_MARKER + sseContent }, finish_reason: 'stop' }] });
|
|
1168
1268
|
sseLine(res, { id: 'pay-err-done', object: 'chat.completion.chunk', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] });
|
|
1169
1269
|
sseDone(res);
|
|
1170
1270
|
res.end();
|
|
@@ -1251,7 +1351,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1251
1351
|
log('paytaca pay failed: ' + err.message);
|
|
1252
1352
|
if (res.headersSent && !res.destroyed && !res.writableEnded) {
|
|
1253
1353
|
try {
|
|
1254
|
-
sseLine(res, { id: 'pay-err', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'deepseek/deepseek-v4-flash', choices: [{ index: 0, delta: { content: '\\n\\n❌ Payment failed: ' + err.message }, finish_reason: 'stop' }] });
|
|
1354
|
+
sseLine(res, { id: 'pay-err', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'deepseek/deepseek-v4-flash', choices: [{ index: 0, delta: { content: PROXY_MARKER + '\\n\\n❌ Payment failed: ' + err.message }, finish_reason: 'stop' }] });
|
|
1255
1355
|
sseLine(res, { id: 'pay-err-done', object: 'chat.completion.chunk', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] });
|
|
1256
1356
|
sseDone(res);
|
|
1257
1357
|
res.end();
|
|
@@ -1275,7 +1375,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1275
1375
|
const errDetails = isTimeout ? 'Try again or check credits with \\'credits\\'.' : 'Please check your balance and try again.';
|
|
1276
1376
|
if (res.headersSent && !res.destroyed && !res.writableEnded) {
|
|
1277
1377
|
try {
|
|
1278
|
-
sseLine(res, { id: 'pay-err', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'deepseek/deepseek-v4-flash', choices: [{ index: 0, delta: { content: sseContent }, finish_reason: 'stop' }] });
|
|
1378
|
+
sseLine(res, { id: 'pay-err', object: 'chat.completion.chunk', created: Math.floor(Date.now() / 1000), model: 'deepseek/deepseek-v4-flash', choices: [{ index: 0, delta: { content: PROXY_MARKER + sseContent }, finish_reason: 'stop' }] });
|
|
1279
1379
|
sseLine(res, { id: 'pay-err-done', object: 'chat.completion.chunk', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] });
|
|
1280
1380
|
sseDone(res);
|
|
1281
1381
|
res.end();
|
|
@@ -1338,7 +1438,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1338
1438
|
index: 0,
|
|
1339
1439
|
message: {
|
|
1340
1440
|
role: 'assistant',
|
|
1341
|
-
content: 'Payment declined. Chat cannot continue without funding.\\n\\n' + fundMsg,
|
|
1441
|
+
content: PROXY_MARKER + 'Payment declined. Chat cannot continue without funding.\\n\\n' + fundMsg,
|
|
1342
1442
|
},
|
|
1343
1443
|
finish_reason: 'stop',
|
|
1344
1444
|
}],
|
|
@@ -1412,6 +1512,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1412
1512
|
}
|
|
1413
1513
|
|
|
1414
1514
|
pendingPayments.set(walletHash, {
|
|
1515
|
+
reqId: proxyReqId,
|
|
1415
1516
|
body: body,
|
|
1416
1517
|
modelId: modelId,
|
|
1417
1518
|
displayName: displayName,
|
|
@@ -1421,8 +1522,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
1421
1522
|
});
|
|
1422
1523
|
|
|
1423
1524
|
if (tiers && tiers.length > 0) {
|
|
1424
|
-
// New flow: show tier selection prompt
|
|
1425
|
-
|
|
1525
|
+
// New flow: show tier selection prompt. Also tell the user about
|
|
1526
|
+
// other models that still have paid credits, so they can switch
|
|
1527
|
+
// instead of buying a plan for the currently selected model.
|
|
1528
|
+
const otherModels = await getOtherModelsWithCredits(walletHash, modelId || requestModel);
|
|
1529
|
+
await streamTierSelectionPrompt(res, walletHash, displayName || modelId || 'AI Model', tiers, otherModels);
|
|
1426
1530
|
return;
|
|
1427
1531
|
}
|
|
1428
1532
|
|
|
@@ -1486,16 +1590,23 @@ const server = http.createServer(async (req, res) => {
|
|
|
1486
1590
|
+ ' tokensUsed=' + tokensUsed
|
|
1487
1591
|
+ ' tokenLimit=' + tokenLimit);
|
|
1488
1592
|
|
|
1489
|
-
await
|
|
1593
|
+
const lowBalanceOtherModels = await getOtherModelsWithCredits(walletHash, statusModelId || modelId);
|
|
1594
|
+
await streamLowBalanceNotice(res, displayName || statusModelId || modelId || 'AI Model', lowBalanceOtherModels);
|
|
1490
1595
|
} else {
|
|
1491
1596
|
if (res.headersSent) {
|
|
1492
1597
|
log('Streaming response completed and already sent');
|
|
1493
|
-
pendingPayments.
|
|
1598
|
+
const settled = pendingPayments.get(walletHash);
|
|
1599
|
+
if (settled && settled.reqId === proxyReqId) {
|
|
1600
|
+
pendingPayments.delete(walletHash);
|
|
1601
|
+
}
|
|
1494
1602
|
return;
|
|
1495
1603
|
}
|
|
1496
1604
|
|
|
1497
1605
|
log('Forwarding normal response to OpenCode: status=' + statusCode + ', bodyLen=' + responseBody.length);
|
|
1498
|
-
pendingPayments.
|
|
1606
|
+
const settled = pendingPayments.get(walletHash);
|
|
1607
|
+
if (settled && settled.reqId === proxyReqId) {
|
|
1608
|
+
pendingPayments.delete(walletHash);
|
|
1609
|
+
}
|
|
1499
1610
|
res.writeHead(statusCode, {
|
|
1500
1611
|
'Content-Type': headers['content-type'] || 'application/json',
|
|
1501
1612
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/bundled/proxy.ts"],"names":[],"mappings":";AAAA,0DAA0D;AAC1D,6DAA6D;;;AAEhD,QAAA,oBAAoB,GAAG
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/bundled/proxy.ts"],"names":[],"mappings":";AAAA,0DAA0D;AAC1D,6DAA6D;;;AAEhD,QAAA,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAonDnC,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -10,4 +10,5 @@ export declare function getPidFile(configDir: string): string;
|
|
|
10
10
|
export declare function getProxyScript(configDir: string): string;
|
|
11
11
|
export declare function getLogFile(configDir: string): string;
|
|
12
12
|
export declare function getWrapperScript(configDir: string): string;
|
|
13
|
+
export declare function getMcpScript(configDir: string): string;
|
|
13
14
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,eAAO,MAAM,mBAAmB,2BAA2B,CAAC;AAC5D,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAGvC,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAIvD;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAkCpD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAIlE;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|
package/dist/config.js
CHANGED
|
@@ -43,15 +43,17 @@ exports.getPidFile = getPidFile;
|
|
|
43
43
|
exports.getProxyScript = getProxyScript;
|
|
44
44
|
exports.getLogFile = getLogFile;
|
|
45
45
|
exports.getWrapperScript = getWrapperScript;
|
|
46
|
+
exports.getMcpScript = getMcpScript;
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
48
|
+
const os = __importStar(require("os"));
|
|
47
49
|
const path = __importStar(require("path"));
|
|
48
50
|
// Default production backend
|
|
49
51
|
exports.DEFAULT_BACKEND_URL = 'https://api.paytaca.ai';
|
|
50
52
|
exports.DEFAULT_PROXY_PORT = 8001;
|
|
51
53
|
// Config directory: ~/.opencode-paytaca/
|
|
52
54
|
function getConfigDir() {
|
|
53
|
-
|
|
54
|
-
return path.join(
|
|
55
|
+
// os.homedir() resolves HOME/USERPROFILE correctly on all platforms.
|
|
56
|
+
return path.join(os.homedir(), '.opencode-paytaca');
|
|
55
57
|
}
|
|
56
58
|
function ensureConfigDir(configDir) {
|
|
57
59
|
if (!fs.existsSync(configDir)) {
|
|
@@ -111,4 +113,7 @@ function getLogFile(configDir) {
|
|
|
111
113
|
function getWrapperScript(configDir) {
|
|
112
114
|
return path.join(configDir, 'paytaca-pay-wrapper.mjs');
|
|
113
115
|
}
|
|
116
|
+
function getMcpScript(configDir) {
|
|
117
|
+
return path.join(configDir, 'mcp-server.js');
|
|
118
|
+
}
|
|
114
119
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,YAAY,QAAsD,CAAC;AAKhF,eAAO,MAAM,oBAAoB,uEAAwD,CAAC;AAgE1F,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAqBzD"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PAYMENT_SUCCESS_LINE = exports.PROXY_MARKER = void 0;
|
|
4
|
+
exports.filterProxyChatter = filterProxyChatter;
|
|
5
|
+
// Zero-width marker the proxy prepends to every synthetic message it streams
|
|
6
|
+
// (tier-selection prompts, credits/plans output, payment notices). The
|
|
7
|
+
// messages stay visible in the opencode UI — zero-width characters don't
|
|
8
|
+
// render — but carrying the marker lets this plugin strip them from the
|
|
9
|
+
// context passed to the LLM, since proxy/payment chatter is not relevant to
|
|
10
|
+
// the coding session.
|
|
11
|
+
exports.PROXY_MARKER = String.fromCharCode(0x200b, 0x200b, 0x200b, 0x200b);
|
|
12
|
+
// The success line the proxy prepends to a real model response after a
|
|
13
|
+
// payment completes. Useful during streaming UX, but it is proxy chatter —
|
|
14
|
+
// stripped from assistant text before the LLM sees it.
|
|
15
|
+
exports.PAYMENT_SUCCESS_LINE = '💳 Payment successful — generating your response...';
|
|
16
|
+
// Replies that are part of a proxy interactive flow (tier pick, approval,
|
|
17
|
+
// credits/plans shortcuts). Only removed when they immediately follow a
|
|
18
|
+
// marked proxy message, so genuine user messages are never dropped.
|
|
19
|
+
const SELECTION_RE = /^\s*(?:\d{1,3}|yes|no|credits|plans|balance)\s*$/i;
|
|
20
|
+
function stripSystemReminders(text) {
|
|
21
|
+
let r = text || '';
|
|
22
|
+
const open = '<system-reminder>';
|
|
23
|
+
const close = '</system-reminder>';
|
|
24
|
+
let i = r.indexOf(open);
|
|
25
|
+
while (i !== -1) {
|
|
26
|
+
const j = r.indexOf(close, i);
|
|
27
|
+
if (j === -1)
|
|
28
|
+
break;
|
|
29
|
+
r = r.substring(0, i) + r.substring(j + close.length);
|
|
30
|
+
i = r.indexOf(open);
|
|
31
|
+
}
|
|
32
|
+
return r;
|
|
33
|
+
}
|
|
34
|
+
function textOf(msg) {
|
|
35
|
+
const parts = msg && msg.parts;
|
|
36
|
+
if (!Array.isArray(parts))
|
|
37
|
+
return '';
|
|
38
|
+
let out = '';
|
|
39
|
+
for (const part of parts) {
|
|
40
|
+
if (part && part.type === 'text' && typeof part.text === 'string') {
|
|
41
|
+
out += part.text;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function isMarkedProxyMessage(msg) {
|
|
47
|
+
return !!(msg && msg.info && msg.info.role === 'assistant' && textOf(msg).indexOf(exports.PROXY_MARKER) !== -1);
|
|
48
|
+
}
|
|
49
|
+
function isSelectionReply(msg) {
|
|
50
|
+
if (!msg || !msg.info || msg.info.role !== 'user')
|
|
51
|
+
return false;
|
|
52
|
+
return SELECTION_RE.test(stripSystemReminders(textOf(msg)).trim());
|
|
53
|
+
}
|
|
54
|
+
// Remove the payment-success preamble from an assistant message without
|
|
55
|
+
// mutating opencode's stored objects — returns a shallow-cloned wrapper when
|
|
56
|
+
// a change is made, or null when the message is clean.
|
|
57
|
+
function withoutPaymentSuccessLine(msg) {
|
|
58
|
+
const parts = msg && msg.parts;
|
|
59
|
+
if (!Array.isArray(parts))
|
|
60
|
+
return null;
|
|
61
|
+
for (let i = 0; i < parts.length; i++) {
|
|
62
|
+
const part = parts[i];
|
|
63
|
+
if (part && part.type === 'text' && typeof part.text === 'string' && part.text.indexOf(exports.PAYMENT_SUCCESS_LINE) !== -1) {
|
|
64
|
+
const cleaned = part.text.split(exports.PAYMENT_SUCCESS_LINE).join('').replace(/\n{3,}/g, '\n\n');
|
|
65
|
+
const newParts = parts.slice();
|
|
66
|
+
newParts[i] = { ...part, text: cleaned };
|
|
67
|
+
return { ...msg, parts: newParts };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// Drop proxy-generated assistant messages (and the interactive selection
|
|
73
|
+
// replies directly following them) from the LLM context. The final message —
|
|
74
|
+
// the turn currently being answered — is always kept, so the proxy can still
|
|
75
|
+
// detect tier selections and approval replies in the live request.
|
|
76
|
+
function filterProxyChatter(messages) {
|
|
77
|
+
if (!Array.isArray(messages) || messages.length === 0)
|
|
78
|
+
return messages;
|
|
79
|
+
const kept = [];
|
|
80
|
+
for (let i = 0; i < messages.length; i++) {
|
|
81
|
+
const msg = messages[i];
|
|
82
|
+
const isLast = i === messages.length - 1;
|
|
83
|
+
if (!isLast && isMarkedProxyMessage(msg)) {
|
|
84
|
+
const next = messages[i + 1];
|
|
85
|
+
if (next && (i + 1) < messages.length - 1 && isSelectionReply(next)) {
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (msg && msg.info && msg.info.role === 'assistant') {
|
|
91
|
+
const cleaned = withoutPaymentSuccessLine(msg);
|
|
92
|
+
kept.push(cleaned || msg);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
kept.push(msg);
|
|
96
|
+
}
|
|
97
|
+
return kept;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
declare function OpencodePlugin(_input?: any, _options?: any): Promise<{
|
|
2
2
|
config?: undefined;
|
|
3
3
|
"chat.headers"?: undefined;
|
|
4
|
+
"experimental.chat.messages.transform"?: undefined;
|
|
4
5
|
} | {
|
|
5
6
|
config: (cfg: any) => Promise<void>;
|
|
6
7
|
"chat.headers": (_input: any, output: any) => Promise<void>;
|
|
8
|
+
"experimental.chat.messages.transform": (_input: any, output: any) => Promise<void>;
|
|
7
9
|
}>;
|
|
8
10
|
declare const _default: {
|
|
9
11
|
id: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,iBAAe,cAAc,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,GAAG;;;;;kBA+HlC,GAAG;6BAsFQ,GAAG,UAAU,GAAG;qDA6BQ,GAAG,UAAU,GAAG;GAiB1E;;;;;AAED,kBAAoE"}
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
const config_1 = require("./config");
|
|
36
36
|
const wallet_1 = require("./wallet");
|
|
37
37
|
const proxy_1 = require("./proxy");
|
|
38
|
+
const mcp_1 = require("./bundled/mcp");
|
|
39
|
+
const context_1 = require("./context");
|
|
40
|
+
const selfheal_1 = require("./selfheal");
|
|
38
41
|
const fs = __importStar(require("fs"));
|
|
39
42
|
const path = __importStar(require("path"));
|
|
40
43
|
const os = __importStar(require("os"));
|
|
@@ -42,6 +45,9 @@ async function OpencodePlugin(_input, _options) {
|
|
|
42
45
|
const configDir = (0, config_1.getConfigDir)();
|
|
43
46
|
(0, config_1.ensureConfigDir)(configDir);
|
|
44
47
|
let config = (0, config_1.loadConfig)(configDir);
|
|
48
|
+
// Best-effort: if a newer version is published, clear stale pins/caches so
|
|
49
|
+
// the next install or session picks it up. Never blocks startup.
|
|
50
|
+
void (0, selfheal_1.runSelfHeal)();
|
|
45
51
|
// Ensure paytaca binary is on PATH for internal use
|
|
46
52
|
(0, wallet_1.ensurePaytacaOnPath)();
|
|
47
53
|
// Check if paytaca-cli is installed
|
|
@@ -112,6 +118,17 @@ async function OpencodePlugin(_input, _options) {
|
|
|
112
118
|
}
|
|
113
119
|
// Start or reuse proxy
|
|
114
120
|
const proxy = await (0, proxy_1.startProxy)(configDir, config);
|
|
121
|
+
// Write the MCP server script so opencode can spawn it (registered in the
|
|
122
|
+
// config hook below). It exposes read-only account tools (credits, balance,
|
|
123
|
+
// models, plans) so the assistant can answer account questions directly.
|
|
124
|
+
const mcpScript = (0, config_1.getMcpScript)(configDir);
|
|
125
|
+
try {
|
|
126
|
+
fs.writeFileSync(mcpScript, mcp_1.MCP_SERVER_CONTENT, 'utf8');
|
|
127
|
+
fs.chmodSync(mcpScript, '755');
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
console.error('Failed to write MCP server script:', e.message);
|
|
131
|
+
}
|
|
115
132
|
// Auto-install paytaca-wallet skill globally (copy from paytaca-cli dependency)
|
|
116
133
|
try {
|
|
117
134
|
const skillCandidates = [
|
|
@@ -197,6 +214,32 @@ async function OpencodePlugin(_input, _options) {
|
|
|
197
214
|
'X-Wallet-Hash': cachedWalletHash,
|
|
198
215
|
};
|
|
199
216
|
}
|
|
217
|
+
// Register the local MCP server so the assistant can answer questions
|
|
218
|
+
// about credits, balance, models, and plans with real account data.
|
|
219
|
+
// opencode spawns it automatically and loads its tools into the session.
|
|
220
|
+
cfg.mcp = cfg.mcp || {};
|
|
221
|
+
cfg.mcp['paytaca'] = {
|
|
222
|
+
type: 'local',
|
|
223
|
+
command: ['node', mcpScript],
|
|
224
|
+
environment: {
|
|
225
|
+
PAYTACA_CONFIG_DIR: configDir,
|
|
226
|
+
PAYTACA_CMD: (0, proxy_1.getPaytacaCommand)(),
|
|
227
|
+
PAYTACA_BACKEND_URL: config.backendUrl || '',
|
|
228
|
+
},
|
|
229
|
+
enabled: true,
|
|
230
|
+
};
|
|
231
|
+
// The MCP send and buy_plan tools move real funds — make opencode
|
|
232
|
+
// prompt the user before they run (tool names are <server>_<tool> =
|
|
233
|
+
// 'paytaca_send' / 'paytaca_buy_plan'). A user configured choice is
|
|
234
|
+
// respected; only unset defaults become 'ask'.
|
|
235
|
+
const permissions = (cfg.permission || {});
|
|
236
|
+
if (permissions['paytaca_send'] === undefined) {
|
|
237
|
+
permissions['paytaca_send'] = 'ask';
|
|
238
|
+
}
|
|
239
|
+
if (permissions['paytaca_buy_plan'] === undefined) {
|
|
240
|
+
permissions['paytaca_buy_plan'] = 'ask';
|
|
241
|
+
}
|
|
242
|
+
cfg.permission = permissions;
|
|
200
243
|
},
|
|
201
244
|
"chat.headers": async (_input, output) => {
|
|
202
245
|
// Secondary fallback delivery path. Never send an empty value —
|
|
@@ -227,6 +270,23 @@ async function OpencodePlugin(_input, _options) {
|
|
|
227
270
|
// Leave headers untouched — the proxy will surface the missing header.
|
|
228
271
|
}
|
|
229
272
|
},
|
|
273
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
274
|
+
// Keep proxy/payment chatter (tier prompts, credits/plans output,
|
|
275
|
+
// payment notices and their selection replies) out of the context sent
|
|
276
|
+
// to the LLM. The messages remain in the session UI for the user.
|
|
277
|
+
try {
|
|
278
|
+
if (output && Array.isArray(output.messages) && output.messages.length > 0) {
|
|
279
|
+
const filtered = (0, context_1.filterProxyChatter)(output.messages);
|
|
280
|
+
output.messages.length = 0;
|
|
281
|
+
for (const m of filtered) {
|
|
282
|
+
output.messages.push(m);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
console.error('Failed to filter proxy chatter from context:', e.message);
|
|
288
|
+
}
|
|
289
|
+
},
|
|
230
290
|
};
|
|
231
291
|
}
|
|
232
292
|
module.exports = { id: '@paytaca/opencode-plugin', server: OpencodePlugin };
|