opencode-gateway 0.2.2 → 0.2.4
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/dist/cli.js +0 -0
- package/dist/index.js +20907 -52
- package/dist/telegram/client.d.ts +1 -1
- package/dist/telegram/poller.d.ts +16 -1
- package/dist/telegram/runtime.d.ts +3 -1
- package/dist/telegram/state.d.ts +7 -0
- package/package.json +1 -1
- package/dist/binding/execution.js +0 -1
- package/dist/binding/gateway.js +0 -1
- package/dist/binding/index.js +0 -4
- package/dist/binding/opencode.js +0 -1
- package/dist/cli/args.js +0 -53
- package/dist/cli/doctor.js +0 -49
- package/dist/cli/init.js +0 -40
- package/dist/cli/opencode-config-file.js +0 -18
- package/dist/cli/opencode-config.js +0 -194
- package/dist/cli/paths.js +0 -22
- package/dist/cli/templates.js +0 -41
- package/dist/config/cron.js +0 -52
- package/dist/config/gateway.js +0 -148
- package/dist/config/memory.js +0 -105
- package/dist/config/paths.js +0 -39
- package/dist/config/telegram.js +0 -91
- package/dist/cron/runtime.js +0 -402
- package/dist/delivery/telegram.js +0 -75
- package/dist/delivery/text.js +0 -175
- package/dist/gateway.js +0 -117
- package/dist/host/file-sender.js +0 -59
- package/dist/host/logger.js +0 -53
- package/dist/host/transport.js +0 -35
- package/dist/mailbox/router.js +0 -16
- package/dist/media/mime.js +0 -45
- package/dist/memory/prompt.js +0 -122
- package/dist/opencode/adapter.js +0 -340
- package/dist/opencode/driver-hub.js +0 -82
- package/dist/opencode/event-normalize.js +0 -48
- package/dist/opencode/event-stream.js +0 -65
- package/dist/opencode/events.js +0 -1
- package/dist/questions/client.js +0 -36
- package/dist/questions/format.js +0 -36
- package/dist/questions/normalize.js +0 -45
- package/dist/questions/parser.js +0 -96
- package/dist/questions/runtime.js +0 -195
- package/dist/questions/types.js +0 -1
- package/dist/runtime/attachments.js +0 -12
- package/dist/runtime/conversation-coordinator.js +0 -22
- package/dist/runtime/executor.js +0 -407
- package/dist/runtime/mailbox.js +0 -112
- package/dist/runtime/opencode-runner.js +0 -79
- package/dist/runtime/runtime-singleton.js +0 -28
- package/dist/session/context.js +0 -23
- package/dist/session/conversation-key.js +0 -3
- package/dist/session/switcher.js +0 -59
- package/dist/session/system-prompt.js +0 -52
- package/dist/store/migrations.js +0 -197
- package/dist/store/sqlite.js +0 -777
- package/dist/telegram/client.js +0 -179
- package/dist/telegram/media.js +0 -65
- package/dist/telegram/normalize.js +0 -119
- package/dist/telegram/poller.js +0 -97
- package/dist/telegram/runtime.js +0 -133
- package/dist/telegram/state.js +0 -128
- package/dist/telegram/types.js +0 -1
- package/dist/tools/channel-new-session.js +0 -27
- package/dist/tools/channel-send-file.js +0 -27
- package/dist/tools/channel-target.js +0 -34
- package/dist/tools/cron-run.js +0 -20
- package/dist/tools/cron-upsert.js +0 -51
- package/dist/tools/gateway-dispatch-cron.js +0 -33
- package/dist/tools/gateway-status.js +0 -25
- package/dist/tools/schedule-cancel.js +0 -12
- package/dist/tools/schedule-format.js +0 -48
- package/dist/tools/schedule-list.js +0 -17
- package/dist/tools/schedule-once.js +0 -43
- package/dist/tools/schedule-status.js +0 -23
- package/dist/tools/telegram-send-test.js +0 -26
- package/dist/tools/telegram-status.js +0 -49
- package/dist/tools/time.js +0 -25
- package/dist/utils/error.js +0 -57
package/dist/telegram/state.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
const TELEGRAM_UPDATE_OFFSET_KEY = "telegram.update_offset";
|
|
2
|
-
const TELEGRAM_LAST_POLL_SUCCESS_MS_KEY = "telegram.last_poll_success_ms";
|
|
3
|
-
const TELEGRAM_LAST_POLL_ERROR_AT_MS_KEY = "telegram.last_poll_error_at_ms";
|
|
4
|
-
const TELEGRAM_LAST_POLL_ERROR_MESSAGE_KEY = "telegram.last_poll_error_message";
|
|
5
|
-
const TELEGRAM_LAST_SEND_SUCCESS_MS_KEY = "telegram.last_send_success_ms";
|
|
6
|
-
const TELEGRAM_LAST_SEND_ERROR_AT_MS_KEY = "telegram.last_send_error_at_ms";
|
|
7
|
-
const TELEGRAM_LAST_SEND_ERROR_MESSAGE_KEY = "telegram.last_send_error_message";
|
|
8
|
-
const TELEGRAM_LAST_PROBE_SUCCESS_MS_KEY = "telegram.last_probe_success_ms";
|
|
9
|
-
const TELEGRAM_LAST_PROBE_ERROR_AT_MS_KEY = "telegram.last_probe_error_at_ms";
|
|
10
|
-
const TELEGRAM_LAST_PROBE_ERROR_MESSAGE_KEY = "telegram.last_probe_error_message";
|
|
11
|
-
const TELEGRAM_LAST_BOT_ID_KEY = "telegram.last_bot_id";
|
|
12
|
-
const TELEGRAM_LAST_BOT_USERNAME_KEY = "telegram.last_bot_username";
|
|
13
|
-
const TELEGRAM_LAST_DRAFT_SUCCESS_MS_KEY = "telegram.last_draft_success_ms";
|
|
14
|
-
const TELEGRAM_LAST_DRAFT_ERROR_AT_MS_KEY = "telegram.last_draft_error_at_ms";
|
|
15
|
-
const TELEGRAM_LAST_DRAFT_ERROR_MESSAGE_KEY = "telegram.last_draft_error_message";
|
|
16
|
-
const TELEGRAM_LAST_PREVIEW_EMIT_MS_KEY = "telegram.last_preview_emit_ms";
|
|
17
|
-
const TELEGRAM_LAST_STREAM_FALLBACK_AT_MS_KEY = "telegram.last_stream_fallback_at_ms";
|
|
18
|
-
const TELEGRAM_LAST_STREAM_FALLBACK_REASON_KEY = "telegram.last_stream_fallback_reason";
|
|
19
|
-
export function readTelegramHealthSnapshot(store) {
|
|
20
|
-
return {
|
|
21
|
-
updateOffset: readStoredInteger(store, TELEGRAM_UPDATE_OFFSET_KEY),
|
|
22
|
-
lastPollSuccessMs: readStoredInteger(store, TELEGRAM_LAST_POLL_SUCCESS_MS_KEY),
|
|
23
|
-
lastPollErrorAtMs: readStoredInteger(store, TELEGRAM_LAST_POLL_ERROR_AT_MS_KEY),
|
|
24
|
-
lastPollErrorMessage: readStoredText(store, TELEGRAM_LAST_POLL_ERROR_MESSAGE_KEY),
|
|
25
|
-
lastSendSuccessMs: readStoredInteger(store, TELEGRAM_LAST_SEND_SUCCESS_MS_KEY),
|
|
26
|
-
lastSendErrorAtMs: readStoredInteger(store, TELEGRAM_LAST_SEND_ERROR_AT_MS_KEY),
|
|
27
|
-
lastSendErrorMessage: readStoredText(store, TELEGRAM_LAST_SEND_ERROR_MESSAGE_KEY),
|
|
28
|
-
lastProbeSuccessMs: readStoredInteger(store, TELEGRAM_LAST_PROBE_SUCCESS_MS_KEY),
|
|
29
|
-
lastProbeErrorAtMs: readStoredInteger(store, TELEGRAM_LAST_PROBE_ERROR_AT_MS_KEY),
|
|
30
|
-
lastProbeErrorMessage: readStoredText(store, TELEGRAM_LAST_PROBE_ERROR_MESSAGE_KEY),
|
|
31
|
-
lastBotId: readStoredText(store, TELEGRAM_LAST_BOT_ID_KEY),
|
|
32
|
-
lastBotUsername: readStoredText(store, TELEGRAM_LAST_BOT_USERNAME_KEY),
|
|
33
|
-
lastDraftSuccessMs: readStoredInteger(store, TELEGRAM_LAST_DRAFT_SUCCESS_MS_KEY),
|
|
34
|
-
lastDraftErrorAtMs: readStoredInteger(store, TELEGRAM_LAST_DRAFT_ERROR_AT_MS_KEY),
|
|
35
|
-
lastDraftErrorMessage: readStoredText(store, TELEGRAM_LAST_DRAFT_ERROR_MESSAGE_KEY),
|
|
36
|
-
lastPreviewEmitMs: readStoredInteger(store, TELEGRAM_LAST_PREVIEW_EMIT_MS_KEY),
|
|
37
|
-
lastStreamFallbackAtMs: readStoredInteger(store, TELEGRAM_LAST_STREAM_FALLBACK_AT_MS_KEY),
|
|
38
|
-
lastStreamFallbackReason: readStoredFallbackReason(store),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
export function recordTelegramPollSuccess(store, recordedAtMs) {
|
|
42
|
-
store.putStateValue(TELEGRAM_LAST_POLL_SUCCESS_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
43
|
-
store.putStateValue(TELEGRAM_LAST_POLL_ERROR_AT_MS_KEY, "", recordedAtMs);
|
|
44
|
-
store.putStateValue(TELEGRAM_LAST_POLL_ERROR_MESSAGE_KEY, "", recordedAtMs);
|
|
45
|
-
}
|
|
46
|
-
export function recordTelegramPollFailure(store, message, recordedAtMs) {
|
|
47
|
-
store.putStateValue(TELEGRAM_LAST_POLL_ERROR_AT_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
48
|
-
store.putStateValue(TELEGRAM_LAST_POLL_ERROR_MESSAGE_KEY, message, recordedAtMs);
|
|
49
|
-
}
|
|
50
|
-
export function recordTelegramSendSuccess(store, recordedAtMs) {
|
|
51
|
-
store.putStateValue(TELEGRAM_LAST_SEND_SUCCESS_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
52
|
-
store.putStateValue(TELEGRAM_LAST_SEND_ERROR_AT_MS_KEY, "", recordedAtMs);
|
|
53
|
-
store.putStateValue(TELEGRAM_LAST_SEND_ERROR_MESSAGE_KEY, "", recordedAtMs);
|
|
54
|
-
}
|
|
55
|
-
export function recordTelegramSendFailure(store, message, recordedAtMs) {
|
|
56
|
-
store.putStateValue(TELEGRAM_LAST_SEND_ERROR_AT_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
57
|
-
store.putStateValue(TELEGRAM_LAST_SEND_ERROR_MESSAGE_KEY, message, recordedAtMs);
|
|
58
|
-
}
|
|
59
|
-
export function recordTelegramProbeSuccess(store, bot, recordedAtMs) {
|
|
60
|
-
store.putStateValue(TELEGRAM_LAST_PROBE_SUCCESS_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
61
|
-
store.putStateValue(TELEGRAM_LAST_PROBE_ERROR_AT_MS_KEY, "", recordedAtMs);
|
|
62
|
-
store.putStateValue(TELEGRAM_LAST_PROBE_ERROR_MESSAGE_KEY, "", recordedAtMs);
|
|
63
|
-
store.putStateValue(TELEGRAM_LAST_BOT_ID_KEY, String(bot.id), recordedAtMs);
|
|
64
|
-
store.putStateValue(TELEGRAM_LAST_BOT_USERNAME_KEY, bot.username ?? "", recordedAtMs);
|
|
65
|
-
}
|
|
66
|
-
export function recordTelegramProbeFailure(store, message, recordedAtMs) {
|
|
67
|
-
store.putStateValue(TELEGRAM_LAST_PROBE_ERROR_AT_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
68
|
-
store.putStateValue(TELEGRAM_LAST_PROBE_ERROR_MESSAGE_KEY, message, recordedAtMs);
|
|
69
|
-
}
|
|
70
|
-
export function recordTelegramDraftSuccess(store, recordedAtMs) {
|
|
71
|
-
store.putStateValue(TELEGRAM_LAST_DRAFT_SUCCESS_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
72
|
-
store.putStateValue(TELEGRAM_LAST_DRAFT_ERROR_AT_MS_KEY, "", recordedAtMs);
|
|
73
|
-
store.putStateValue(TELEGRAM_LAST_DRAFT_ERROR_MESSAGE_KEY, "", recordedAtMs);
|
|
74
|
-
}
|
|
75
|
-
export function recordTelegramDraftFailure(store, message, recordedAtMs) {
|
|
76
|
-
store.putStateValue(TELEGRAM_LAST_DRAFT_ERROR_AT_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
77
|
-
store.putStateValue(TELEGRAM_LAST_DRAFT_ERROR_MESSAGE_KEY, message, recordedAtMs);
|
|
78
|
-
}
|
|
79
|
-
export function recordTelegramPreviewEmit(store, recordedAtMs) {
|
|
80
|
-
store.putStateValue(TELEGRAM_LAST_PREVIEW_EMIT_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
81
|
-
}
|
|
82
|
-
export function recordTelegramStreamFallback(store, reason, recordedAtMs) {
|
|
83
|
-
store.putStateValue(TELEGRAM_LAST_STREAM_FALLBACK_AT_MS_KEY, String(recordedAtMs), recordedAtMs);
|
|
84
|
-
store.putStateValue(TELEGRAM_LAST_STREAM_FALLBACK_REASON_KEY, reason, recordedAtMs);
|
|
85
|
-
}
|
|
86
|
-
export function readTelegramChatType(store, chatId) {
|
|
87
|
-
const value = readStoredText(store, telegramChatTypeKey(chatId));
|
|
88
|
-
return value === null ? null : value;
|
|
89
|
-
}
|
|
90
|
-
export function recordTelegramChatType(store, chatId, chatType, recordedAtMs) {
|
|
91
|
-
store.putStateValue(telegramChatTypeKey(chatId), chatType, recordedAtMs);
|
|
92
|
-
}
|
|
93
|
-
function telegramChatTypeKey(chatId) {
|
|
94
|
-
return `telegram.chat_type:${chatId}`;
|
|
95
|
-
}
|
|
96
|
-
function readStoredInteger(store, key) {
|
|
97
|
-
const value = store.getStateValue(key);
|
|
98
|
-
if (value === null || value.length === 0) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
const parsed = Number.parseInt(value, 10);
|
|
102
|
-
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
103
|
-
throw new Error(`stored ${key} is invalid: ${value}`);
|
|
104
|
-
}
|
|
105
|
-
return parsed;
|
|
106
|
-
}
|
|
107
|
-
function readStoredText(store, key) {
|
|
108
|
-
const value = store.getStateValue(key);
|
|
109
|
-
if (value === null || value.length === 0) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
return value;
|
|
113
|
-
}
|
|
114
|
-
function readStoredFallbackReason(store) {
|
|
115
|
-
const value = readStoredText(store, TELEGRAM_LAST_STREAM_FALLBACK_REASON_KEY);
|
|
116
|
-
if (value === null) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
switch (value) {
|
|
120
|
-
case "non_private_chat":
|
|
121
|
-
case "progressive_handle_unavailable":
|
|
122
|
-
case "draft_send_failed":
|
|
123
|
-
case "preview_not_established":
|
|
124
|
-
return value;
|
|
125
|
-
default:
|
|
126
|
-
throw new Error(`stored ${TELEGRAM_LAST_STREAM_FALLBACK_REASON_KEY} is invalid: ${value}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
package/dist/telegram/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { resolveToolDeliveryTarget } from "./channel-target";
|
|
3
|
-
export function createChannelNewSessionTool(switcher, sessions) {
|
|
4
|
-
return tool({
|
|
5
|
-
description: "Create a fresh OpenCode session for a channel route and switch future inbound messages to it. When called from a channel-backed session, channel, target, and topic default to the current reply target.",
|
|
6
|
-
args: {
|
|
7
|
-
channel: tool.schema.string().min(1).optional(),
|
|
8
|
-
target: tool.schema.string().min(1).optional(),
|
|
9
|
-
topic: tool.schema.string().optional(),
|
|
10
|
-
title: tool.schema.string().optional(),
|
|
11
|
-
},
|
|
12
|
-
async execute(args, context) {
|
|
13
|
-
return formatChannelSessionSwitchResult(await switcher.createAndSwitchSession(resolveToolDeliveryTarget(args, context.sessionID, sessions), args.title ?? null));
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
function formatChannelSessionSwitchResult(result) {
|
|
18
|
-
return [
|
|
19
|
-
`channel=${result.channel}`,
|
|
20
|
-
`target=${result.target}`,
|
|
21
|
-
`topic=${result.topic ?? "none"}`,
|
|
22
|
-
`conversation_key=${result.conversationKey}`,
|
|
23
|
-
`previous_session_id=${result.previousSessionId ?? "none"}`,
|
|
24
|
-
`new_session_id=${result.newSessionId}`,
|
|
25
|
-
`effective_on=${result.effectiveOn}`,
|
|
26
|
-
].join("\n");
|
|
27
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { resolveToolDeliveryTarget } from "./channel-target";
|
|
3
|
-
export function createChannelSendFileTool(sender, sessions) {
|
|
4
|
-
return tool({
|
|
5
|
-
description: "Send a local absolute-path file to a channel target. When called from a channel-backed session, channel, target, and topic default to the current reply target.",
|
|
6
|
-
args: {
|
|
7
|
-
channel: tool.schema.string().min(1).optional(),
|
|
8
|
-
target: tool.schema.string().min(1).optional(),
|
|
9
|
-
topic: tool.schema.string().optional(),
|
|
10
|
-
file_path: tool.schema.string().min(1),
|
|
11
|
-
caption: tool.schema.string().optional(),
|
|
12
|
-
},
|
|
13
|
-
async execute(args, context) {
|
|
14
|
-
return formatChannelFileSendResult(await sender.sendFile(resolveToolDeliveryTarget(args, context.sessionID, sessions), args.file_path, args.caption ?? null));
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function formatChannelFileSendResult(result) {
|
|
19
|
-
return [
|
|
20
|
-
`channel=${result.channel}`,
|
|
21
|
-
`target=${result.target}`,
|
|
22
|
-
`topic=${result.topic ?? "none"}`,
|
|
23
|
-
`file_path=${result.filePath}`,
|
|
24
|
-
`mime_type=${result.mimeType}`,
|
|
25
|
-
`delivery_kind=${result.deliveryKind}`,
|
|
26
|
-
].join("\n");
|
|
27
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export function resolveToolDeliveryTarget(args, sessionId, sessions) {
|
|
2
|
-
const fallback = sessionId ? sessions.getDefaultReplyTarget(sessionId) : null;
|
|
3
|
-
const channel = normalizeRequired(args.channel ?? fallback?.channel ?? null, "channel");
|
|
4
|
-
const target = normalizeRequired(args.target ?? fallback?.target ?? null, "target");
|
|
5
|
-
const topic = normalizeOptional(args.topic ?? fallback?.topic ?? null);
|
|
6
|
-
return {
|
|
7
|
-
channel,
|
|
8
|
-
target,
|
|
9
|
-
topic,
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
export function resolveOptionalToolDeliveryTarget(args, sessionId, sessions) {
|
|
13
|
-
if (args.channel === undefined && args.target === undefined && args.topic === undefined) {
|
|
14
|
-
return sessionId ? sessions.getDefaultReplyTarget(sessionId) : null;
|
|
15
|
-
}
|
|
16
|
-
return resolveToolDeliveryTarget(args, sessionId, sessions);
|
|
17
|
-
}
|
|
18
|
-
function normalizeRequired(value, field) {
|
|
19
|
-
if (value === null) {
|
|
20
|
-
throw new Error(`${field} is required when the current session has no default reply target`);
|
|
21
|
-
}
|
|
22
|
-
const trimmed = value.trim();
|
|
23
|
-
if (trimmed.length === 0) {
|
|
24
|
-
throw new Error(`${field} must not be empty`);
|
|
25
|
-
}
|
|
26
|
-
return trimmed;
|
|
27
|
-
}
|
|
28
|
-
function normalizeOptional(value) {
|
|
29
|
-
if (value === null) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
const trimmed = value.trim();
|
|
33
|
-
return trimmed.length === 0 ? null : trimmed;
|
|
34
|
-
}
|
package/dist/tools/cron-run.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
export function createCronRunTool(runtime) {
|
|
3
|
-
return tool({
|
|
4
|
-
description: "Run one persisted gateway schedule job immediately without changing its schedule metadata.",
|
|
5
|
-
args: {
|
|
6
|
-
id: tool.schema.string().min(1),
|
|
7
|
-
},
|
|
8
|
-
async execute(args) {
|
|
9
|
-
return formatRuntimeReport(await runtime.runNow(args.id));
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
function formatRuntimeReport(report) {
|
|
14
|
-
return [
|
|
15
|
-
`conversation_key=${report.conversationKey}`,
|
|
16
|
-
`response_text=${report.responseText}`,
|
|
17
|
-
`delivered=${report.delivered}`,
|
|
18
|
-
`recorded_at_ms=${report.recordedAtMs}`,
|
|
19
|
-
].join("\n");
|
|
20
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { resolveOptionalToolDeliveryTarget } from "./channel-target";
|
|
3
|
-
import { formatUnixMsAsUtc, formatUnixMsInTimeZone } from "./time";
|
|
4
|
-
export function createCronUpsertTool(runtime, sessions) {
|
|
5
|
-
return tool({
|
|
6
|
-
description: "Create or replace a recurring gateway cron job. When called from a channel-backed session, delivery defaults to the current reply target.",
|
|
7
|
-
args: {
|
|
8
|
-
id: tool.schema.string().min(1),
|
|
9
|
-
schedule: tool.schema.string().min(1),
|
|
10
|
-
prompt: tool.schema.string().min(1),
|
|
11
|
-
enabled: tool.schema.boolean().optional(),
|
|
12
|
-
delivery_channel: tool.schema.string().optional(),
|
|
13
|
-
delivery_target: tool.schema.string().optional(),
|
|
14
|
-
delivery_topic: tool.schema.string().optional(),
|
|
15
|
-
},
|
|
16
|
-
async execute(args, context) {
|
|
17
|
-
const deliveryTarget = resolveOptionalToolDeliveryTarget({
|
|
18
|
-
channel: args.delivery_channel,
|
|
19
|
-
target: args.delivery_target,
|
|
20
|
-
topic: args.delivery_topic,
|
|
21
|
-
}, context.sessionID, sessions);
|
|
22
|
-
const timeZone = runtime.timeZone();
|
|
23
|
-
const job = runtime.upsertJob({
|
|
24
|
-
id: args.id,
|
|
25
|
-
schedule: args.schedule,
|
|
26
|
-
prompt: args.prompt,
|
|
27
|
-
enabled: args.enabled ?? true,
|
|
28
|
-
deliveryChannel: deliveryTarget?.channel ?? null,
|
|
29
|
-
deliveryTarget: deliveryTarget?.target ?? null,
|
|
30
|
-
deliveryTopic: deliveryTarget?.topic ?? null,
|
|
31
|
-
});
|
|
32
|
-
return [
|
|
33
|
-
`id=${job.id}`,
|
|
34
|
-
`kind=${job.kind}`,
|
|
35
|
-
`enabled=${job.enabled}`,
|
|
36
|
-
`schedule=${job.schedule}`,
|
|
37
|
-
`timezone=${timeZone}`,
|
|
38
|
-
`next_run_at_ms=${job.nextRunAtMs}`,
|
|
39
|
-
`next_run_at_local=${formatUnixMsInTimeZone(job.nextRunAtMs, timeZone)}`,
|
|
40
|
-
`next_run_at_utc=${formatUnixMsAsUtc(job.nextRunAtMs)}`,
|
|
41
|
-
`delivery=${formatDelivery(job.deliveryChannel, job.deliveryTarget, job.deliveryTopic)}`,
|
|
42
|
-
].join("\n");
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
function formatDelivery(channel, target, topic) {
|
|
47
|
-
if (channel === null || target === null) {
|
|
48
|
-
return "none";
|
|
49
|
-
}
|
|
50
|
-
return topic === null ? `${channel}:${target}` : `${channel}:${target}:topic:${topic}`;
|
|
51
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
export function createGatewayDispatchCronTool(executor) {
|
|
3
|
-
return tool({
|
|
4
|
-
description: "Dispatch one gateway cron-style prompt through the gateway runtime",
|
|
5
|
-
args: {
|
|
6
|
-
id: tool.schema.string().min(1),
|
|
7
|
-
schedule: tool.schema.string().min(1),
|
|
8
|
-
prompt: tool.schema.string().min(1),
|
|
9
|
-
},
|
|
10
|
-
async execute(args) {
|
|
11
|
-
const report = await executor.dispatchCronJob(toCronJobSpec(args));
|
|
12
|
-
return formatRuntimeReport(report);
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
function toCronJobSpec(args) {
|
|
17
|
-
return {
|
|
18
|
-
id: args.id,
|
|
19
|
-
schedule: args.schedule,
|
|
20
|
-
prompt: args.prompt,
|
|
21
|
-
deliveryChannel: null,
|
|
22
|
-
deliveryTarget: null,
|
|
23
|
-
deliveryTopic: null,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function formatRuntimeReport(report) {
|
|
27
|
-
return [
|
|
28
|
-
`conversation_key=${report.conversationKey}`,
|
|
29
|
-
`response_text=${report.responseText}`,
|
|
30
|
-
`delivered=${report.delivered}`,
|
|
31
|
-
`recorded_at_ms=${report.recordedAtMs}`,
|
|
32
|
-
].join("\n");
|
|
33
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
export function createGatewayStatusTool(runtime) {
|
|
3
|
-
return tool({
|
|
4
|
-
description: "Return the current Rust gateway contract status",
|
|
5
|
-
args: {},
|
|
6
|
-
async execute() {
|
|
7
|
-
return formatGatewayStatus(runtime.status());
|
|
8
|
-
},
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
function formatGatewayStatus(status) {
|
|
12
|
-
return [
|
|
13
|
-
`runtime_mode=${status.runtimeMode}`,
|
|
14
|
-
`supports_telegram=${status.supportsTelegram}`,
|
|
15
|
-
`supports_cron=${status.supportsCron}`,
|
|
16
|
-
`has_web_ui=${status.hasWebUi}`,
|
|
17
|
-
`cron_timezone=${status.cronTimezone}`,
|
|
18
|
-
`cron_enabled=${status.cronEnabled}`,
|
|
19
|
-
`cron_polling=${status.cronPolling}`,
|
|
20
|
-
`cron_running_jobs=${status.cronRunningJobs}`,
|
|
21
|
-
`telegram_enabled=${status.telegramEnabled}`,
|
|
22
|
-
`telegram_polling=${status.telegramPolling}`,
|
|
23
|
-
`telegram_allowlist_mode=${status.telegramAllowlistMode}`,
|
|
24
|
-
].join("\n");
|
|
25
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
export function createScheduleCancelTool(runtime) {
|
|
3
|
-
return tool({
|
|
4
|
-
description: "Cancel a persisted gateway schedule job without deleting its run history.",
|
|
5
|
-
args: {
|
|
6
|
-
id: tool.schema.string().min(1),
|
|
7
|
-
},
|
|
8
|
-
async execute(args) {
|
|
9
|
-
return runtime.cancelJob(args.id) ? `canceled=${args.id}` : `inactive=${args.id}`;
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { formatUnixMsAsUtc, formatUnixMsInTimeZone } from "./time";
|
|
2
|
-
export function formatScheduleJob(job, timeZone) {
|
|
3
|
-
const lines = [`id=${job.id}`, `kind=${job.kind}`, `enabled=${job.enabled}`];
|
|
4
|
-
if (job.kind === "cron") {
|
|
5
|
-
lines.push(`schedule=${job.schedule}`);
|
|
6
|
-
lines.push(`timezone=${timeZone}`);
|
|
7
|
-
lines.push(`next_run_at_ms=${job.nextRunAtMs}`);
|
|
8
|
-
lines.push(`next_run_at_local=${formatUnixMsInTimeZone(job.nextRunAtMs, timeZone)}`);
|
|
9
|
-
lines.push(`next_run_at_utc=${formatUnixMsAsUtc(job.nextRunAtMs)}`);
|
|
10
|
-
}
|
|
11
|
-
else {
|
|
12
|
-
lines.push(`run_at_ms=${job.runAtMs ?? "none"}`);
|
|
13
|
-
if (job.runAtMs !== null) {
|
|
14
|
-
lines.push(`run_at_local=${formatUnixMsInTimeZone(job.runAtMs, timeZone)}`);
|
|
15
|
-
lines.push(`run_at_utc=${formatUnixMsAsUtc(job.runAtMs)}`);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
lines.push(`delivery=${formatDelivery(job.deliveryChannel, job.deliveryTarget, job.deliveryTopic)}`);
|
|
19
|
-
lines.push(`prompt=${job.prompt}`);
|
|
20
|
-
return lines.join("\n");
|
|
21
|
-
}
|
|
22
|
-
export function formatScheduleStatus(status, timeZone) {
|
|
23
|
-
const lines = [formatScheduleJob(status.job, timeZone), `state=${status.state}`];
|
|
24
|
-
if (status.runs.length === 0) {
|
|
25
|
-
lines.push("runs=none");
|
|
26
|
-
return lines.join("\n");
|
|
27
|
-
}
|
|
28
|
-
lines.push("");
|
|
29
|
-
lines.push(...status.runs.map((run, index) => formatRun(run, index + 1)));
|
|
30
|
-
return lines.join("\n");
|
|
31
|
-
}
|
|
32
|
-
function formatRun(run, ordinal) {
|
|
33
|
-
return [
|
|
34
|
-
`run[${ordinal}].id=${run.id}`,
|
|
35
|
-
`run[${ordinal}].status=${run.status}`,
|
|
36
|
-
`run[${ordinal}].scheduled_for_ms=${run.scheduledForMs}`,
|
|
37
|
-
`run[${ordinal}].started_at_ms=${run.startedAtMs}`,
|
|
38
|
-
`run[${ordinal}].finished_at_ms=${run.finishedAtMs ?? "none"}`,
|
|
39
|
-
`run[${ordinal}].response_text=${run.responseText ?? "none"}`,
|
|
40
|
-
`run[${ordinal}].error_message=${run.errorMessage ?? "none"}`,
|
|
41
|
-
].join("\n");
|
|
42
|
-
}
|
|
43
|
-
function formatDelivery(channel, target, topic) {
|
|
44
|
-
if (channel === null || target === null) {
|
|
45
|
-
return "none";
|
|
46
|
-
}
|
|
47
|
-
return topic === null ? `${channel}:${target}` : `${channel}:${target}:topic:${topic}`;
|
|
48
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { formatScheduleJob } from "./schedule-format";
|
|
3
|
-
export function createScheduleListTool(runtime) {
|
|
4
|
-
return tool({
|
|
5
|
-
description: "List persisted gateway schedule jobs, including recurring cron jobs and one-shot timers.",
|
|
6
|
-
args: {
|
|
7
|
-
include_terminal: tool.schema.boolean().optional(),
|
|
8
|
-
},
|
|
9
|
-
async execute(args) {
|
|
10
|
-
const jobs = runtime.listJobs(args.include_terminal ?? false);
|
|
11
|
-
if (jobs.length === 0) {
|
|
12
|
-
return "no scheduled jobs";
|
|
13
|
-
}
|
|
14
|
-
return jobs.map((job) => formatScheduleJob(job, runtime.timeZone())).join("\n\n");
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { resolveOptionalToolDeliveryTarget } from "./channel-target";
|
|
3
|
-
import { formatScheduleJob } from "./schedule-format";
|
|
4
|
-
export function createScheduleOnceTool(runtime, sessions) {
|
|
5
|
-
return tool({
|
|
6
|
-
description: "Schedule a one-shot gateway job. When called from a channel-backed session, delivery defaults to the current reply target.",
|
|
7
|
-
args: {
|
|
8
|
-
id: tool.schema.string().min(1),
|
|
9
|
-
prompt: tool.schema.string().min(1),
|
|
10
|
-
delay_seconds: tool.schema.number().optional(),
|
|
11
|
-
run_at_ms: tool.schema.number().optional(),
|
|
12
|
-
delivery_channel: tool.schema.string().optional(),
|
|
13
|
-
delivery_target: tool.schema.string().optional(),
|
|
14
|
-
delivery_topic: tool.schema.string().optional(),
|
|
15
|
-
},
|
|
16
|
-
async execute(args, context) {
|
|
17
|
-
const deliveryTarget = resolveOptionalToolDeliveryTarget({
|
|
18
|
-
channel: args.delivery_channel,
|
|
19
|
-
target: args.delivery_target,
|
|
20
|
-
topic: args.delivery_topic,
|
|
21
|
-
}, context.sessionID, sessions);
|
|
22
|
-
const job = runtime.scheduleOnce({
|
|
23
|
-
id: args.id,
|
|
24
|
-
prompt: args.prompt,
|
|
25
|
-
delaySeconds: normalizeOptionalInteger(args.delay_seconds, "delay_seconds"),
|
|
26
|
-
runAtMs: normalizeOptionalInteger(args.run_at_ms, "run_at_ms"),
|
|
27
|
-
deliveryChannel: deliveryTarget?.channel ?? null,
|
|
28
|
-
deliveryTarget: deliveryTarget?.target ?? null,
|
|
29
|
-
deliveryTopic: deliveryTarget?.topic ?? null,
|
|
30
|
-
});
|
|
31
|
-
return formatScheduleJob(job, runtime.timeZone());
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function normalizeOptionalInteger(value, field) {
|
|
36
|
-
if (value === undefined) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
if (!Number.isSafeInteger(value)) {
|
|
40
|
-
throw new Error(`${field} must be an integer`);
|
|
41
|
-
}
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { formatScheduleStatus } from "./schedule-format";
|
|
3
|
-
export function createScheduleStatusTool(runtime) {
|
|
4
|
-
return tool({
|
|
5
|
-
description: "Inspect one persisted gateway schedule job and its recent run history.",
|
|
6
|
-
args: {
|
|
7
|
-
id: tool.schema.string().min(1),
|
|
8
|
-
limit: tool.schema.number().optional(),
|
|
9
|
-
},
|
|
10
|
-
async execute(args) {
|
|
11
|
-
return formatScheduleStatus(runtime.getJobStatus(args.id, normalizeOptionalLimit(args.limit)), runtime.timeZone());
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
function normalizeOptionalLimit(value) {
|
|
16
|
-
if (value === undefined) {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
if (!Number.isSafeInteger(value)) {
|
|
20
|
-
throw new Error("limit must be an integer");
|
|
21
|
-
}
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { formatUnixMsAsUtc } from "./time";
|
|
3
|
-
export function createTelegramSendTestTool(runtime) {
|
|
4
|
-
return tool({
|
|
5
|
-
description: "Send a Telegram test message to an explicit chat_id and optional topic",
|
|
6
|
-
args: {
|
|
7
|
-
chat_id: tool.schema.string().min(1),
|
|
8
|
-
topic: tool.schema.string().optional(),
|
|
9
|
-
text: tool.schema.string().optional(),
|
|
10
|
-
mode: tool.schema.enum(["auto", "oneshot", "stream"]).optional(),
|
|
11
|
-
},
|
|
12
|
-
async execute(args) {
|
|
13
|
-
return formatTelegramSendTestResult(await runtime.sendTest(args.chat_id, args.topic ?? null, args.text ?? null, args.mode ?? "auto"));
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
function formatTelegramSendTestResult(result) {
|
|
18
|
-
return [
|
|
19
|
-
`chat_id=${result.chatId}`,
|
|
20
|
-
`topic=${result.topic ?? "none"}`,
|
|
21
|
-
`mode=${result.mode}`,
|
|
22
|
-
`sent_at_ms=${result.sentAtMs}`,
|
|
23
|
-
`sent_at_utc=${formatUnixMsAsUtc(result.sentAtMs)}`,
|
|
24
|
-
`text=${result.text}`,
|
|
25
|
-
].join("\n");
|
|
26
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { formatOptionalUnixMsAsUtc } from "./time";
|
|
3
|
-
export function createTelegramStatusTool(runtime) {
|
|
4
|
-
return tool({
|
|
5
|
-
description: "Return Telegram gateway status, cached health, and a live Bot API probe",
|
|
6
|
-
args: {},
|
|
7
|
-
async execute() {
|
|
8
|
-
return formatTelegramStatus(await runtime.status());
|
|
9
|
-
},
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
function formatTelegramStatus(status) {
|
|
13
|
-
return [
|
|
14
|
-
`enabled=${status.enabled}`,
|
|
15
|
-
`polling=${status.polling}`,
|
|
16
|
-
`allowlist_mode=${status.allowlistMode}`,
|
|
17
|
-
`allowed_chats_count=${status.allowedChatsCount}`,
|
|
18
|
-
`allowed_users_count=${status.allowedUsersCount}`,
|
|
19
|
-
`update_offset=${status.updateOffset ?? "none"}`,
|
|
20
|
-
`last_poll_success_ms=${status.lastPollSuccessMs ?? "none"}`,
|
|
21
|
-
`last_poll_success_utc=${formatOptionalUnixMsAsUtc(status.lastPollSuccessMs)}`,
|
|
22
|
-
`last_poll_error_at_ms=${status.lastPollErrorAtMs ?? "none"}`,
|
|
23
|
-
`last_poll_error=${status.lastPollErrorMessage ?? "none"}`,
|
|
24
|
-
`last_send_success_ms=${status.lastSendSuccessMs ?? "none"}`,
|
|
25
|
-
`last_send_success_utc=${formatOptionalUnixMsAsUtc(status.lastSendSuccessMs)}`,
|
|
26
|
-
`last_send_error_at_ms=${status.lastSendErrorAtMs ?? "none"}`,
|
|
27
|
-
`last_send_error=${status.lastSendErrorMessage ?? "none"}`,
|
|
28
|
-
`last_probe_success_ms=${status.lastProbeSuccessMs ?? "none"}`,
|
|
29
|
-
`last_probe_success_utc=${formatOptionalUnixMsAsUtc(status.lastProbeSuccessMs)}`,
|
|
30
|
-
`last_probe_error_at_ms=${status.lastProbeErrorAtMs ?? "none"}`,
|
|
31
|
-
`last_probe_error=${status.lastProbeErrorMessage ?? "none"}`,
|
|
32
|
-
`live_probe=${status.liveProbe}`,
|
|
33
|
-
`live_probe_error=${status.liveProbeError ?? "none"}`,
|
|
34
|
-
`bot_id=${status.liveBotId ?? status.lastBotId ?? "none"}`,
|
|
35
|
-
`bot_username=${status.liveBotUsername ?? status.lastBotUsername ?? "none"}`,
|
|
36
|
-
`streaming_enabled=${status.streamingEnabled}`,
|
|
37
|
-
`opencode_event_stream_connected=${status.opencodeEventStreamConnected}`,
|
|
38
|
-
`last_event_stream_error=${status.lastEventStreamError ?? "none"}`,
|
|
39
|
-
`last_draft_success_ms=${status.lastDraftSuccessMs ?? "none"}`,
|
|
40
|
-
`last_draft_success_utc=${formatOptionalUnixMsAsUtc(status.lastDraftSuccessMs)}`,
|
|
41
|
-
`last_draft_error_at_ms=${status.lastDraftErrorAtMs ?? "none"}`,
|
|
42
|
-
`last_draft_error=${status.lastDraftErrorMessage ?? "none"}`,
|
|
43
|
-
`last_preview_emit_ms=${status.lastPreviewEmitMs ?? "none"}`,
|
|
44
|
-
`last_preview_emit_utc=${formatOptionalUnixMsAsUtc(status.lastPreviewEmitMs)}`,
|
|
45
|
-
`last_stream_fallback_at_ms=${status.lastStreamFallbackAtMs ?? "none"}`,
|
|
46
|
-
`last_stream_fallback_utc=${formatOptionalUnixMsAsUtc(status.lastStreamFallbackAtMs)}`,
|
|
47
|
-
`last_stream_fallback_reason=${status.lastStreamFallbackReason ?? "none"}`,
|
|
48
|
-
].join("\n");
|
|
49
|
-
}
|
package/dist/tools/time.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export function formatUnixMsAsUtc(value) {
|
|
2
|
-
return new Date(value).toISOString();
|
|
3
|
-
}
|
|
4
|
-
export function formatOptionalUnixMsAsUtc(value) {
|
|
5
|
-
return value === null ? "none" : formatUnixMsAsUtc(value);
|
|
6
|
-
}
|
|
7
|
-
export function formatUnixMsInTimeZone(value, timeZone) {
|
|
8
|
-
const formatter = new Intl.DateTimeFormat("en-CA", {
|
|
9
|
-
timeZone,
|
|
10
|
-
year: "numeric",
|
|
11
|
-
month: "2-digit",
|
|
12
|
-
day: "2-digit",
|
|
13
|
-
hour: "2-digit",
|
|
14
|
-
minute: "2-digit",
|
|
15
|
-
second: "2-digit",
|
|
16
|
-
hour12: false,
|
|
17
|
-
});
|
|
18
|
-
const parts = formatter.formatToParts(new Date(value));
|
|
19
|
-
const values = new Map(parts.map((part) => [part.type, part.value]));
|
|
20
|
-
return [
|
|
21
|
-
`${values.get("year")}-${values.get("month")}-${values.get("day")}`,
|
|
22
|
-
`${values.get("hour")}:${values.get("minute")}:${values.get("second")}`,
|
|
23
|
-
`[${timeZone}]`,
|
|
24
|
-
].join(" ");
|
|
25
|
-
}
|
package/dist/utils/error.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export function formatError(error) {
|
|
2
|
-
if (error instanceof Error) {
|
|
3
|
-
const errorLike = error;
|
|
4
|
-
const nestedMessage = extractNestedDataMessage(errorLike.data);
|
|
5
|
-
if (nestedMessage !== null) {
|
|
6
|
-
return nestedMessage;
|
|
7
|
-
}
|
|
8
|
-
return error.message;
|
|
9
|
-
}
|
|
10
|
-
if (typeof error === "string") {
|
|
11
|
-
return error;
|
|
12
|
-
}
|
|
13
|
-
if (typeof error === "object" && error !== null) {
|
|
14
|
-
const errorLike = error;
|
|
15
|
-
const nestedMessage = extractNestedDataMessage(errorLike.data);
|
|
16
|
-
if (nestedMessage !== null) {
|
|
17
|
-
return nestedMessage;
|
|
18
|
-
}
|
|
19
|
-
if (typeof errorLike.message === "string" && errorLike.message.length > 0) {
|
|
20
|
-
const detail = compactObject({
|
|
21
|
-
name: errorLike.name,
|
|
22
|
-
code: errorLike.code,
|
|
23
|
-
status: errorLike.status,
|
|
24
|
-
cause: errorLike.cause,
|
|
25
|
-
});
|
|
26
|
-
return detail === null ? errorLike.message : `${errorLike.message} (${detail})`;
|
|
27
|
-
}
|
|
28
|
-
const serialized = safeJsonStringify(error);
|
|
29
|
-
if (serialized !== null) {
|
|
30
|
-
return serialized;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return String(error);
|
|
34
|
-
}
|
|
35
|
-
function extractNestedDataMessage(value) {
|
|
36
|
-
if (typeof value !== "object" || value === null) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
const message = value.message;
|
|
40
|
-
return typeof message === "string" && message.length > 0 ? message : null;
|
|
41
|
-
}
|
|
42
|
-
function compactObject(value) {
|
|
43
|
-
const entries = Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined && fieldValue !== null);
|
|
44
|
-
if (entries.length === 0) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
const serialized = safeJsonStringify(Object.fromEntries(entries));
|
|
48
|
-
return serialized ?? entries.map(([key, fieldValue]) => `${key}=${String(fieldValue)}`).join(", ");
|
|
49
|
-
}
|
|
50
|
-
function safeJsonStringify(value) {
|
|
51
|
-
try {
|
|
52
|
-
return JSON.stringify(value);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|