@stagewhisper/stagewhisper 0.49.0 → 0.52.0
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/index.js +5250 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +12 -11
- package/api.ts +0 -4
- package/index.ts +0 -54
- package/openresponses.d.ts +0 -1361
- package/plugin-main.ts +0 -342
- package/src/channel.test.ts +0 -201
- package/src/channel.ts +0 -178
- package/src/client.ts +0 -144
- package/src/health.test.ts +0 -101
- package/src/health.ts +0 -94
- package/src/openresponses.ts +0 -107
- package/src/reasoning.test.ts +0 -116
- package/src/reasoning.ts +0 -210
- package/src/runtime.ts +0 -6
- package/src/service.ts +0 -662
package/src/channel.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createChatChannelPlugin,
|
|
3
|
-
createChannelPluginBase,
|
|
4
|
-
DEFAULT_ACCOUNT_ID,
|
|
5
|
-
} from "openclaw/plugin-sdk/core";
|
|
6
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
7
|
-
|
|
8
|
-
export type StageWhisperAccount = {
|
|
9
|
-
accountId: string | null;
|
|
10
|
-
apiBaseUrl: string;
|
|
11
|
-
integrationId: string;
|
|
12
|
-
relayToken: string;
|
|
13
|
-
label: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function getChannelSection(cfg: OpenClawConfig): Record<string, unknown> {
|
|
17
|
-
const channels = cfg.channels as
|
|
18
|
-
| Record<string, Record<string, unknown>>
|
|
19
|
-
| undefined;
|
|
20
|
-
return channels?.["stagewhisper"] ?? {};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function resolveAccount(
|
|
24
|
-
cfg: OpenClawConfig,
|
|
25
|
-
accountId?: string | null,
|
|
26
|
-
): StageWhisperAccount {
|
|
27
|
-
const section = getChannelSection(cfg);
|
|
28
|
-
const pluginCfg = (cfg as Record<string, unknown>)?.["plugins"] as
|
|
29
|
-
| Record<string, unknown>
|
|
30
|
-
| undefined;
|
|
31
|
-
const entries = pluginCfg?.["entries"] as
|
|
32
|
-
| Record<string, Record<string, unknown>>
|
|
33
|
-
| undefined;
|
|
34
|
-
const swConfig = entries?.["stagewhisper"]?.["config"] as
|
|
35
|
-
| Record<string, unknown>
|
|
36
|
-
| undefined;
|
|
37
|
-
|
|
38
|
-
const apiBaseUrl =
|
|
39
|
-
(section["apiBaseUrl"] as string) ||
|
|
40
|
-
(swConfig?.["apiBaseUrl"] as string) ||
|
|
41
|
-
"https://api.stagewhisper.io";
|
|
42
|
-
const integrationId =
|
|
43
|
-
(section["integrationId"] as string) ??
|
|
44
|
-
(swConfig?.["integrationId"] as string) ??
|
|
45
|
-
"";
|
|
46
|
-
const relayToken =
|
|
47
|
-
(section["relayToken"] as string) ??
|
|
48
|
-
(swConfig?.["relayToken"] as string) ??
|
|
49
|
-
"";
|
|
50
|
-
const label =
|
|
51
|
-
(section["label"] as string) ??
|
|
52
|
-
(swConfig?.["label"] as string) ??
|
|
53
|
-
"StageWhisper";
|
|
54
|
-
|
|
55
|
-
if (!apiBaseUrl) throw new Error("stagewhisper: apiBaseUrl is required");
|
|
56
|
-
if (!integrationId)
|
|
57
|
-
throw new Error("stagewhisper: integrationId is required");
|
|
58
|
-
if (!relayToken) throw new Error("stagewhisper: relayToken is required");
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
accountId: accountId ?? null,
|
|
62
|
-
apiBaseUrl,
|
|
63
|
-
integrationId,
|
|
64
|
-
relayToken,
|
|
65
|
-
label,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const base = createChannelPluginBase<StageWhisperAccount>({
|
|
70
|
-
id: "stagewhisper",
|
|
71
|
-
capabilities: {
|
|
72
|
-
chatTypes: ["direct"],
|
|
73
|
-
},
|
|
74
|
-
config: {
|
|
75
|
-
listAccountIds: (cfg: OpenClawConfig) => {
|
|
76
|
-
try {
|
|
77
|
-
resolveAccount(cfg);
|
|
78
|
-
return [DEFAULT_ACCOUNT_ID];
|
|
79
|
-
} catch {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
resolveAccount,
|
|
84
|
-
inspectAccount(cfg: OpenClawConfig) {
|
|
85
|
-
try {
|
|
86
|
-
const account = resolveAccount(cfg);
|
|
87
|
-
return {
|
|
88
|
-
enabled: true,
|
|
89
|
-
configured: true,
|
|
90
|
-
label: account.label,
|
|
91
|
-
apiBaseUrl: account.apiBaseUrl,
|
|
92
|
-
};
|
|
93
|
-
} catch {
|
|
94
|
-
return {
|
|
95
|
-
enabled: false,
|
|
96
|
-
configured: false,
|
|
97
|
-
tokenStatus: "missing",
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
setup: {
|
|
103
|
-
applyAccountConfig({ cfg, input }) {
|
|
104
|
-
const channels = (cfg.channels ?? {}) as Record<
|
|
105
|
-
string,
|
|
106
|
-
Record<string, unknown>
|
|
107
|
-
>;
|
|
108
|
-
const section = { ...(channels["stagewhisper"] ?? {}) };
|
|
109
|
-
const raw = input as unknown as Record<string, string>;
|
|
110
|
-
|
|
111
|
-
if (raw["apiBaseUrl"]) section["apiBaseUrl"] = raw["apiBaseUrl"];
|
|
112
|
-
if (raw["integrationId"])
|
|
113
|
-
section["integrationId"] = raw["integrationId"];
|
|
114
|
-
if (raw["relayToken"]) section["relayToken"] = raw["relayToken"];
|
|
115
|
-
if (raw["label"]) section["label"] = raw["label"];
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
...cfg,
|
|
119
|
-
channels: { ...channels, stagewhisper: section },
|
|
120
|
-
} as OpenClawConfig;
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
export const stagewhisperPlugin = createChatChannelPlugin<StageWhisperAccount>(
|
|
126
|
-
{
|
|
127
|
-
base: base as unknown as Parameters<typeof createChatChannelPlugin<StageWhisperAccount>>[0]["base"],
|
|
128
|
-
|
|
129
|
-
security: {
|
|
130
|
-
dm: {
|
|
131
|
-
channelKey: "stagewhisper",
|
|
132
|
-
resolvePolicy: () => "closed",
|
|
133
|
-
resolveAllowFrom: () => [],
|
|
134
|
-
defaultPolicy: "closed",
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
pairing: {
|
|
139
|
-
text: {
|
|
140
|
-
idLabel: "StageWhisper pairing code",
|
|
141
|
-
message: "Run this command to complete pairing:",
|
|
142
|
-
notify: async () => {},
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
threading: { topLevelReplyToMode: "reply" },
|
|
147
|
-
|
|
148
|
-
outbound: {
|
|
149
|
-
base: {
|
|
150
|
-
deliveryMode: "direct",
|
|
151
|
-
resolveTarget: (params: {
|
|
152
|
-
cfg?: OpenClawConfig;
|
|
153
|
-
to?: string;
|
|
154
|
-
allowFrom?: string[];
|
|
155
|
-
accountId?: string | null;
|
|
156
|
-
mode?: string;
|
|
157
|
-
}) => {
|
|
158
|
-
if (!params.to) return { ok: false as const, error: new Error("No delivery target") };
|
|
159
|
-
return { ok: true as const, to: params.to };
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
attachedResults: {
|
|
163
|
-
channel: "stagewhisper",
|
|
164
|
-
sendText: async (ctx) => {
|
|
165
|
-
const target = (ctx as Record<string, unknown>).to as string | undefined ?? "";
|
|
166
|
-
if (target.startsWith("sw-session-")) {
|
|
167
|
-
return { messageId: `sw-relay-ack-${Date.now()}`, ok: true };
|
|
168
|
-
}
|
|
169
|
-
console.warn(
|
|
170
|
-
`[stagewhisper] sendText called for unrecognised target "${target}"; ` +
|
|
171
|
-
`StageWhisper channel is inbound-only — task replies are routed by the relay service`,
|
|
172
|
-
);
|
|
173
|
-
return { messageId: `sw-dropped-${Date.now()}`, ok: false };
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
);
|
package/src/client.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
export type TaskPayload = {
|
|
2
|
-
id: string;
|
|
3
|
-
session_id: string;
|
|
4
|
-
title: string;
|
|
5
|
-
request_text: string;
|
|
6
|
-
action_type: string;
|
|
7
|
-
status: string;
|
|
8
|
-
evidence_payload: Record<string, unknown> | null;
|
|
9
|
-
created_at: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type PairCompleteResponse = {
|
|
13
|
-
integration_id: string;
|
|
14
|
-
relay_token: string;
|
|
15
|
-
label: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type HeartbeatResponse = {
|
|
19
|
-
ok: boolean;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export class StageWhisperClient {
|
|
23
|
-
private baseUrl: string;
|
|
24
|
-
private integrationId: string;
|
|
25
|
-
private relayToken: string;
|
|
26
|
-
|
|
27
|
-
constructor(baseUrl: string, integrationId: string, relayToken: string) {
|
|
28
|
-
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
29
|
-
this.integrationId = integrationId;
|
|
30
|
-
this.relayToken = relayToken;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private headers(): Record<string, string> {
|
|
34
|
-
return {
|
|
35
|
-
Authorization: `Bearer ${this.relayToken}`,
|
|
36
|
-
"Content-Type": "application/json",
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async completePairing(pairingCode: string, hostLabel: string): Promise<PairCompleteResponse> {
|
|
41
|
-
const res = await fetch(`${this.baseUrl}/api/v1/openclaw/pair/complete`, {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: { "Content-Type": "application/json" },
|
|
44
|
-
body: JSON.stringify({
|
|
45
|
-
pairing_code: pairingCode,
|
|
46
|
-
host_label: hostLabel,
|
|
47
|
-
}),
|
|
48
|
-
});
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
const text = await res.text();
|
|
51
|
-
throw new Error(`Pairing failed (${res.status}): ${text}`);
|
|
52
|
-
}
|
|
53
|
-
return res.json();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async updateTaskStatus(taskId: string, status: string, remoteTaskId?: string): Promise<void> {
|
|
57
|
-
const body: Record<string, unknown> = { status };
|
|
58
|
-
if (remoteTaskId) body.remote_task_id = remoteTaskId;
|
|
59
|
-
|
|
60
|
-
const res = await fetch(`${this.baseUrl}/api/v1/openclaw/tasks/${taskId}/status`, {
|
|
61
|
-
method: "POST",
|
|
62
|
-
headers: this.headers(),
|
|
63
|
-
body: JSON.stringify(body),
|
|
64
|
-
});
|
|
65
|
-
if (!res.ok) {
|
|
66
|
-
const text = await res.text();
|
|
67
|
-
throw new Error(`Status update failed (${res.status}): ${text}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async postReply(taskId: string, content: string, remoteMessageId?: string): Promise<void> {
|
|
72
|
-
const body: Record<string, unknown> = { content };
|
|
73
|
-
if (remoteMessageId) body.remote_message_id = remoteMessageId;
|
|
74
|
-
|
|
75
|
-
const res = await fetch(`${this.baseUrl}/api/v1/openclaw/tasks/${taskId}/reply`, {
|
|
76
|
-
method: "POST",
|
|
77
|
-
headers: this.headers(),
|
|
78
|
-
body: JSON.stringify(body),
|
|
79
|
-
});
|
|
80
|
-
if (!res.ok) {
|
|
81
|
-
const text = await res.text();
|
|
82
|
-
throw new Error(`Reply failed (${res.status}): ${text}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async heartbeat(capabilities?: Record<string, unknown>): Promise<HeartbeatResponse> {
|
|
87
|
-
const res = await fetch(
|
|
88
|
-
`${this.baseUrl}/api/v1/openclaw/integrations/${this.integrationId}/heartbeat`,
|
|
89
|
-
{
|
|
90
|
-
method: "POST",
|
|
91
|
-
headers: this.headers(),
|
|
92
|
-
body: capabilities ? JSON.stringify(capabilities) : undefined,
|
|
93
|
-
},
|
|
94
|
-
);
|
|
95
|
-
if (!res.ok) {
|
|
96
|
-
const text = await res.text();
|
|
97
|
-
throw new Error(`Heartbeat failed (${res.status}): ${text}`);
|
|
98
|
-
}
|
|
99
|
-
return res.json();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async postReasoningResult(
|
|
103
|
-
jobId: string,
|
|
104
|
-
result: Record<string, unknown>,
|
|
105
|
-
correlationId?: string,
|
|
106
|
-
): Promise<void> {
|
|
107
|
-
const headers = this.headers();
|
|
108
|
-
if (correlationId) {
|
|
109
|
-
headers["X-Correlation-ID"] = correlationId;
|
|
110
|
-
}
|
|
111
|
-
const res = await fetch(`${this.baseUrl}/api/v1/openclaw/reasoning-jobs/${jobId}/complete`, {
|
|
112
|
-
method: "POST",
|
|
113
|
-
headers,
|
|
114
|
-
body: JSON.stringify(result),
|
|
115
|
-
});
|
|
116
|
-
if (!res.ok) {
|
|
117
|
-
const text = await res.text();
|
|
118
|
-
throw new Error(`Reasoning result post failed (${res.status}): ${text}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
streamUrl(): string {
|
|
123
|
-
return `${this.baseUrl}/api/v1/openclaw/integrations/${this.integrationId}/stream`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
streamHeaders(): Record<string, string> {
|
|
127
|
-
return { Authorization: `Bearer ${this.relayToken}` };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async testReply(testId: string, content: string): Promise<void> {
|
|
131
|
-
const res = await fetch(
|
|
132
|
-
`${this.baseUrl}/api/v1/openclaw/integrations/${this.integrationId}/test-reply`,
|
|
133
|
-
{
|
|
134
|
-
method: "POST",
|
|
135
|
-
headers: this.headers(),
|
|
136
|
-
body: JSON.stringify({ test_id: testId, content }),
|
|
137
|
-
},
|
|
138
|
-
);
|
|
139
|
-
if (!res.ok) {
|
|
140
|
-
const text = await res.text();
|
|
141
|
-
throw new Error(`Test reply failed (${res.status}): ${text}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
package/src/health.test.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { createHealthTracker } from "./health.js";
|
|
3
|
-
|
|
4
|
-
describe("createHealthTracker", () => {
|
|
5
|
-
it("starts unverified", () => {
|
|
6
|
-
const tracker = createHealthTracker("gpt-4o");
|
|
7
|
-
const caps = tracker.get();
|
|
8
|
-
expect(caps.status).toBe("unverified");
|
|
9
|
-
expect(caps.supportsReasoning).toBe(true);
|
|
10
|
-
expect(caps.displayModel).toBe("gpt-4o");
|
|
11
|
-
expect(caps.consecutiveFailures).toBe(0);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("records success and updates lastHealthyAt", () => {
|
|
15
|
-
const tracker = createHealthTracker(null);
|
|
16
|
-
tracker.recordFailure("timeout");
|
|
17
|
-
tracker.recordSuccess();
|
|
18
|
-
const caps = tracker.get();
|
|
19
|
-
expect(caps.status).toBe("healthy");
|
|
20
|
-
expect(caps.consecutiveFailures).toBe(0);
|
|
21
|
-
expect(caps.lastHealthyAt).toBeInstanceOf(Date);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("degrades after 3 consecutive failures", () => {
|
|
25
|
-
const tracker = createHealthTracker(null);
|
|
26
|
-
tracker.recordFailure("err1");
|
|
27
|
-
tracker.recordFailure("err2");
|
|
28
|
-
expect(tracker.get().status).toBe("unverified");
|
|
29
|
-
tracker.recordFailure("err3");
|
|
30
|
-
expect(tracker.get().status).toBe("degraded");
|
|
31
|
-
expect(tracker.get().degradedReason).toBe("err3");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("sets auth_required on 401/403 errors", () => {
|
|
35
|
-
const tracker = createHealthTracker(null);
|
|
36
|
-
tracker.recordFailure("401 Unauthorized");
|
|
37
|
-
expect(tracker.get().status).toBe("auth_required");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("setModel updates displayModel", () => {
|
|
41
|
-
const tracker = createHealthTracker("gpt-4o");
|
|
42
|
-
tracker.setModel("claude-4");
|
|
43
|
-
expect(tracker.get().displayModel).toBe("claude-4");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("setDisabled marks disabled", () => {
|
|
47
|
-
const tracker = createHealthTracker(null);
|
|
48
|
-
tracker.setDisabled();
|
|
49
|
-
const caps = tracker.get();
|
|
50
|
-
expect(caps.status).toBe("disabled");
|
|
51
|
-
expect(caps.supportsReasoning).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("toHeartbeatPayload serializes snake_case", () => {
|
|
55
|
-
const tracker = createHealthTracker("gpt-4o");
|
|
56
|
-
tracker.recordSuccess();
|
|
57
|
-
const payload = tracker.toHeartbeatPayload();
|
|
58
|
-
expect(payload).toMatchObject({
|
|
59
|
-
supports_assistant_tasks: true,
|
|
60
|
-
supports_reasoning: true,
|
|
61
|
-
reasoning_transport: "openresponses",
|
|
62
|
-
display_model: "gpt-4o",
|
|
63
|
-
provider_ref: "openclaw",
|
|
64
|
-
status: "healthy",
|
|
65
|
-
degraded_reason: null,
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("get returns a copy, not the internal state", () => {
|
|
70
|
-
const tracker = createHealthTracker(null);
|
|
71
|
-
const caps = tracker.get();
|
|
72
|
-
caps.status = "disabled";
|
|
73
|
-
expect(tracker.get().status).toBe("unverified");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("setDisconnected marks disconnected and setConnected restores", () => {
|
|
77
|
-
const tracker = createHealthTracker(null);
|
|
78
|
-
tracker.recordSuccess();
|
|
79
|
-
expect(tracker.get().status).toBe("healthy");
|
|
80
|
-
|
|
81
|
-
tracker.setDisconnected();
|
|
82
|
-
expect(tracker.get().status).toBe("disconnected");
|
|
83
|
-
|
|
84
|
-
tracker.setConnected();
|
|
85
|
-
expect(tracker.get().status).toBe("healthy");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("setDisconnected does not override disabled", () => {
|
|
89
|
-
const tracker = createHealthTracker(null);
|
|
90
|
-
tracker.setDisabled();
|
|
91
|
-
tracker.setDisconnected();
|
|
92
|
-
expect(tracker.get().status).toBe("disabled");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("setConnected is a no-op when not disconnected", () => {
|
|
96
|
-
const tracker = createHealthTracker(null);
|
|
97
|
-
tracker.recordSuccess();
|
|
98
|
-
tracker.setConnected();
|
|
99
|
-
expect(tracker.get().status).toBe("healthy");
|
|
100
|
-
});
|
|
101
|
-
});
|
package/src/health.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export type HostHealthStatus =
|
|
2
|
-
| "healthy"
|
|
3
|
-
| "degraded"
|
|
4
|
-
| "disconnected"
|
|
5
|
-
| "auth_required"
|
|
6
|
-
| "disabled"
|
|
7
|
-
| "unverified";
|
|
8
|
-
|
|
9
|
-
export type HostCapabilities = {
|
|
10
|
-
supportsAssistantTasks: boolean;
|
|
11
|
-
supportsReasoning: boolean;
|
|
12
|
-
reasoningTransport: string | null;
|
|
13
|
-
displayModel: string | null;
|
|
14
|
-
providerRef: string;
|
|
15
|
-
status: HostHealthStatus;
|
|
16
|
-
degradedReason: string | null;
|
|
17
|
-
lastHealthyAt: Date | null;
|
|
18
|
-
consecutiveFailures: number;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const DEGRADED_THRESHOLD = 3;
|
|
22
|
-
|
|
23
|
-
export function createHealthTracker(initialModel: string | null) {
|
|
24
|
-
const state: HostCapabilities = {
|
|
25
|
-
supportsAssistantTasks: true,
|
|
26
|
-
supportsReasoning: true,
|
|
27
|
-
reasoningTransport: "openresponses",
|
|
28
|
-
displayModel: initialModel,
|
|
29
|
-
providerRef: "openclaw",
|
|
30
|
-
status: "unverified",
|
|
31
|
-
degradedReason: null,
|
|
32
|
-
lastHealthyAt: null,
|
|
33
|
-
consecutiveFailures: 0,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
let statusBeforeDisconnect: HostHealthStatus | null = null;
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
get: (): HostCapabilities => ({ ...state }),
|
|
40
|
-
|
|
41
|
-
recordSuccess() {
|
|
42
|
-
state.consecutiveFailures = 0;
|
|
43
|
-
state.status = "healthy";
|
|
44
|
-
state.degradedReason = null;
|
|
45
|
-
state.lastHealthyAt = new Date();
|
|
46
|
-
statusBeforeDisconnect = null;
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
recordFailure(reason: string) {
|
|
50
|
-
state.consecutiveFailures += 1;
|
|
51
|
-
state.degradedReason = reason;
|
|
52
|
-
if (reason.includes("401") || reason.includes("403")) {
|
|
53
|
-
state.status = "auth_required";
|
|
54
|
-
} else if (state.consecutiveFailures >= DEGRADED_THRESHOLD) {
|
|
55
|
-
state.status = "degraded";
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
setModel(model: string) {
|
|
60
|
-
state.displayModel = model;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
setDisabled() {
|
|
64
|
-
state.status = "disabled";
|
|
65
|
-
state.supportsReasoning = false;
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
setDisconnected() {
|
|
69
|
-
if (state.status !== "disabled") {
|
|
70
|
-
statusBeforeDisconnect = state.status;
|
|
71
|
-
state.status = "disconnected";
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
setConnected() {
|
|
76
|
-
if (state.status === "disconnected" && statusBeforeDisconnect !== null) {
|
|
77
|
-
state.status = statusBeforeDisconnect;
|
|
78
|
-
statusBeforeDisconnect = null;
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
toHeartbeatPayload(): Record<string, unknown> {
|
|
83
|
-
return {
|
|
84
|
-
supports_assistant_tasks: state.supportsAssistantTasks,
|
|
85
|
-
supports_reasoning: state.supportsReasoning,
|
|
86
|
-
reasoning_transport: state.reasoningTransport,
|
|
87
|
-
display_model: state.displayModel,
|
|
88
|
-
provider_ref: state.providerRef,
|
|
89
|
-
status: state.status,
|
|
90
|
-
degraded_reason: state.degradedReason,
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
package/src/openresponses.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
|
-
|
|
3
|
-
type GatewayConfig = { url: string; apiKey: string | null };
|
|
4
|
-
|
|
5
|
-
function resolveGatewayConfig(api: OpenClawPluginApi): GatewayConfig {
|
|
6
|
-
const cfg = api.config as Record<string, unknown>;
|
|
7
|
-
const gw = (cfg?.gateway as Record<string, unknown>) ?? {};
|
|
8
|
-
const auth = (gw?.auth as Record<string, unknown>) ?? {};
|
|
9
|
-
|
|
10
|
-
const port = Number(gw?.port) || 18789;
|
|
11
|
-
const explicitUrl = typeof gw?.url === "string" ? gw.url : null;
|
|
12
|
-
const url = (explicitUrl ?? `http://127.0.0.1:${port}`).replace(/\/+$/, "");
|
|
13
|
-
|
|
14
|
-
const token = typeof auth?.token === "string" ? auth.token : null;
|
|
15
|
-
|
|
16
|
-
return { url, apiKey: token };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function isResponsesEndpointEnabled(api: OpenClawPluginApi): boolean {
|
|
20
|
-
const cfg = api.config as Record<string, unknown>;
|
|
21
|
-
const gw = (cfg?.gateway as Record<string, unknown>) ?? {};
|
|
22
|
-
const http = (gw?.http as Record<string, unknown>) ?? {};
|
|
23
|
-
const endpoints = (http?.endpoints as Record<string, unknown>) ?? {};
|
|
24
|
-
const responses = (endpoints?.responses as Record<string, unknown>) ?? {};
|
|
25
|
-
return responses?.enabled === true;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function callOpenResponses(
|
|
29
|
-
api: OpenClawPluginApi,
|
|
30
|
-
requestBody: OpenResponsesCreateResponseRequestBody,
|
|
31
|
-
signal?: AbortSignal,
|
|
32
|
-
correlationId?: string,
|
|
33
|
-
): Promise<OpenResponsesResponseResource> {
|
|
34
|
-
const gw = resolveGatewayConfig(api);
|
|
35
|
-
const url = `${gw.url}/v1/responses`;
|
|
36
|
-
|
|
37
|
-
const headers: Record<string, string> = {
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
};
|
|
40
|
-
if (gw.apiKey) {
|
|
41
|
-
headers["Authorization"] = `Bearer ${gw.apiKey}`;
|
|
42
|
-
}
|
|
43
|
-
if (correlationId) {
|
|
44
|
-
headers["X-Correlation-ID"] = correlationId;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const response = await fetch(url, {
|
|
48
|
-
method: "POST",
|
|
49
|
-
headers,
|
|
50
|
-
body: JSON.stringify(requestBody),
|
|
51
|
-
signal,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
const body = await response.text().catch(() => "");
|
|
56
|
-
if (response.status === 404) {
|
|
57
|
-
throw new OpenResponsesError(
|
|
58
|
-
"POST /v1/responses returned 404 — the OpenResponses HTTP API is most likely disabled. " +
|
|
59
|
-
'Enable it in OpenClaw config: gateway.http.endpoints.responses.enabled = true, then restart the gateway.',
|
|
60
|
-
response.status,
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
throw new OpenResponsesError(`POST /v1/responses returned ${response.status}: ${body}`, response.status);
|
|
64
|
-
}
|
|
65
|
-
return (await response.json()) as OpenResponsesResponseResource;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export class OpenResponsesError extends Error {
|
|
69
|
-
constructor(
|
|
70
|
-
message: string,
|
|
71
|
-
public readonly statusCode: number,
|
|
72
|
-
) {
|
|
73
|
-
super(message);
|
|
74
|
-
this.name = "OpenResponsesError";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
get retryable(): boolean {
|
|
78
|
-
return (
|
|
79
|
-
this.statusCode === 408 ||
|
|
80
|
-
this.statusCode === 429 ||
|
|
81
|
-
(this.statusCode >= 500 && this.statusCode < 600)
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export type { components, operations, paths, webhooks } from "../openresponses";
|
|
87
|
-
|
|
88
|
-
export type OpenResponsesCreateResponseOperation =
|
|
89
|
-
import("../openresponses").operations["createResponse"];
|
|
90
|
-
export type OpenResponsesCreateResponseRequestBody =
|
|
91
|
-
import("../openresponses").components["schemas"]["CreateResponseBody"];
|
|
92
|
-
export type OpenResponsesResponseResource =
|
|
93
|
-
import("../openresponses").components["schemas"]["ResponseResource"];
|
|
94
|
-
export type OpenResponsesRequestItem =
|
|
95
|
-
import("../openresponses").components["schemas"]["ItemParam"];
|
|
96
|
-
export type OpenResponsesResponseItem =
|
|
97
|
-
import("../openresponses").components["schemas"]["ItemField"];
|
|
98
|
-
export type OpenResponsesTool =
|
|
99
|
-
import("../openresponses").components["schemas"]["ResponsesToolParam"];
|
|
100
|
-
export type OpenResponsesToolChoice =
|
|
101
|
-
import("../openresponses").components["schemas"]["ToolChoiceParam"];
|
|
102
|
-
export type OpenResponsesTextFormat =
|
|
103
|
-
import("../openresponses").components["schemas"]["TextFormatParam"];
|
|
104
|
-
export type OpenResponsesReasoning =
|
|
105
|
-
import("../openresponses").components["schemas"]["ReasoningParam"];
|
|
106
|
-
export type OpenResponsesStreamEvent =
|
|
107
|
-
import("../openresponses").operations["createResponse"]["responses"][200]["content"]["text/event-stream"];
|