@cogcoin/client 1.1.1 → 1.1.3

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.
@@ -18,6 +18,14 @@ function createBuiltInProviderNotFoundError(options) {
18
18
  : `${providerLabel} returned HTTP 404 for model "${options.model}". The configured model override may be invalid. Rerun \`cogcoin mine setup\` to clear or correct it.`;
19
19
  return new MiningProviderRequestError("not-found", message);
20
20
  }
21
+ function createBuiltInProviderTimeoutError(options) {
22
+ const providerName = options.provider === "anthropic" ? "Anthropic" : "OpenAI";
23
+ const seconds = Number.isInteger(options.timeoutMs / 1_000)
24
+ ? String(options.timeoutMs / 1_000)
25
+ : (options.timeoutMs / 1_000).toFixed(1);
26
+ const unit = seconds === "1" ? "second" : "seconds";
27
+ return new MiningProviderRequestError("unavailable", `The built-in ${providerName} mining provider timed out after ${seconds} ${unit}.`);
28
+ }
21
29
  function buildSystemPrompt(extraPrompt) {
22
30
  const lines = [
23
31
  "You are helping generate candidate Cogcoin mining sentences.",
@@ -57,25 +65,89 @@ function annotateProviderCandidates(options) {
57
65
  },
58
66
  }));
59
67
  }
60
- function parseProviderJsonResponse(options) {
61
- const response = parseStrictJsonValue(stripMarkdownCodeFence(options.raw), `${options.providerLabel} returned invalid JSON.`);
68
+ const ANTHROPIC_MINING_RESPONSE_TOOL_NAME = "return_mining_candidates";
69
+ const ANTHROPIC_MINING_RESPONSE_TOOL = {
70
+ name: ANTHROPIC_MINING_RESPONSE_TOOL_NAME,
71
+ description: [
72
+ "Return the Cogcoin mining sentence generation result in structured form.",
73
+ "Use this tool exactly once instead of writing prose, markdown, or code fences.",
74
+ "Set schemaVersion to 1, copy the requestId exactly, and include only candidates for domainId values from rootDomains.",
75
+ "Each candidate sentence must be a single natural-language sentence with no surrounding commentary.",
76
+ ].join(" "),
77
+ input_schema: {
78
+ type: "object",
79
+ properties: {
80
+ schemaVersion: { type: "integer" },
81
+ requestId: { type: "string" },
82
+ candidates: {
83
+ type: "array",
84
+ items: {
85
+ type: "object",
86
+ properties: {
87
+ domainId: { type: "integer" },
88
+ sentence: { type: "string" },
89
+ },
90
+ required: ["domainId", "sentence"],
91
+ additionalProperties: false,
92
+ },
93
+ },
94
+ },
95
+ required: ["schemaVersion", "requestId", "candidates"],
96
+ additionalProperties: false,
97
+ },
98
+ };
99
+ function normalizeProviderCandidateResponse(options) {
62
100
  try {
63
101
  return normalizeMiningSentenceResponse({
64
102
  request: options.request,
65
- response,
103
+ response: options.response,
66
104
  }).candidates;
67
105
  }
68
106
  catch (error) {
69
107
  throw new Error(error instanceof Error ? error.message : `${options.providerLabel} returned an invalid response.`);
70
108
  }
71
109
  }
110
+ function parseProviderJsonResponse(options) {
111
+ return normalizeProviderCandidateResponse({
112
+ response: parseStrictJsonValue(stripMarkdownCodeFence(options.raw), `${options.providerLabel} returned invalid JSON.`),
113
+ request: options.request,
114
+ providerLabel: options.providerLabel,
115
+ });
116
+ }
72
117
  function createProviderSignal(signal, timeoutMs) {
73
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
74
- return signal === undefined ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]);
118
+ const controller = new AbortController();
119
+ let didTimeout = false;
120
+ const timer = setTimeout(() => {
121
+ didTimeout = true;
122
+ controller.abort(new DOMException("The operation was aborted due to timeout", "TimeoutError"));
123
+ }, timeoutMs);
124
+ timer.unref?.();
125
+ const handleAbort = () => {
126
+ controller.abort(signal?.reason);
127
+ };
128
+ if (signal !== undefined) {
129
+ if (signal.aborted) {
130
+ handleAbort();
131
+ }
132
+ else {
133
+ signal.addEventListener("abort", handleAbort, { once: true });
134
+ }
135
+ }
136
+ return {
137
+ signal: controller.signal,
138
+ didTimeout() {
139
+ return didTimeout;
140
+ },
141
+ dispose() {
142
+ clearTimeout(timer);
143
+ signal?.removeEventListener("abort", handleAbort);
144
+ },
145
+ };
75
146
  }
76
147
  async function requestBuiltInSentences(options) {
77
148
  const fetchImpl = options.fetchImpl ?? fetch;
78
- const providerSignal = createProviderSignal(options.signal, Math.min(MINING_BUILTIN_TIMEOUT_MS, options.request.limits.timeoutMs));
149
+ const timeoutMs = Math.min(MINING_BUILTIN_TIMEOUT_MS, options.request.limits.timeoutMs);
150
+ const providerSignal = createProviderSignal(options.signal, timeoutMs);
79
151
  try {
80
152
  if (options.provider === "openai") {
81
153
  const { effectiveModel: model, usingDefaultModel } = resolveBuiltInProviderModel(options.provider, options.modelOverride);
@@ -98,7 +170,7 @@ async function requestBuiltInSentences(options) {
98
170
  },
99
171
  ],
100
172
  }),
101
- signal: providerSignal,
173
+ signal: providerSignal.signal,
102
174
  });
103
175
  if (response.status === 401 || response.status === 403) {
104
176
  throw new MiningProviderRequestError("auth-error", "The built-in OpenAI mining provider rejected the configured API key.");
@@ -144,8 +216,13 @@ async function requestBuiltInSentences(options) {
144
216
  content: buildUserPrompt(options.request),
145
217
  },
146
218
  ],
219
+ tools: [ANTHROPIC_MINING_RESPONSE_TOOL],
220
+ tool_choice: {
221
+ type: "tool",
222
+ name: ANTHROPIC_MINING_RESPONSE_TOOL_NAME,
223
+ },
147
224
  }),
148
- signal: providerSignal,
225
+ signal: providerSignal.signal,
149
226
  });
150
227
  if (response.status === 401 || response.status === 403) {
151
228
  throw new MiningProviderRequestError("auth-error", "The built-in Anthropic mining provider rejected the configured API key.");
@@ -164,8 +241,8 @@ async function requestBuiltInSentences(options) {
164
241
  throw new MiningProviderRequestError("unavailable", `The built-in Anthropic mining provider returned HTTP ${response.status}.`);
165
242
  }
166
243
  return annotateProviderCandidates({
167
- candidates: parseProviderJsonResponse({
168
- raw: extractAnthropicText(await response.json()),
244
+ candidates: normalizeProviderCandidateResponse({
245
+ response: extractAnthropicResponsePayload(await response.json()),
169
246
  request: options.request,
170
247
  providerLabel: "The built-in Anthropic mining provider",
171
248
  }),
@@ -177,11 +254,21 @@ async function requestBuiltInSentences(options) {
177
254
  if (error instanceof MiningProviderRequestError) {
178
255
  throw error;
179
256
  }
180
- if (error instanceof Error && error.name === "AbortError") {
181
- throw new MiningProviderRequestError("unavailable", "Mining sentence generation was aborted.");
257
+ if (providerSignal.didTimeout()) {
258
+ throw createBuiltInProviderTimeoutError({
259
+ provider: options.provider,
260
+ timeoutMs,
261
+ });
262
+ }
263
+ if (error instanceof Error
264
+ && (error.name === "AbortError" || error.name === "TimeoutError")) {
265
+ throw error;
182
266
  }
183
267
  throw new MiningProviderRequestError("unavailable", error instanceof Error ? error.message : String(error));
184
268
  }
269
+ finally {
270
+ providerSignal.dispose();
271
+ }
185
272
  }
186
273
  function extractOpenAiText(payload) {
187
274
  if (payload !== null && typeof payload === "object") {
@@ -235,6 +322,24 @@ function extractAnthropicText(payload) {
235
322
  }
236
323
  throw new Error("The built-in Anthropic mining provider returned an empty response.");
237
324
  }
325
+ function extractAnthropicResponsePayload(payload) {
326
+ if (payload !== null && typeof payload === "object") {
327
+ const content = payload.content;
328
+ if (Array.isArray(content)) {
329
+ for (const entry of content) {
330
+ if (entry === null || typeof entry !== "object") {
331
+ continue;
332
+ }
333
+ const typedEntry = entry;
334
+ if (typedEntry.type === "tool_use"
335
+ && typedEntry.name === ANTHROPIC_MINING_RESPONSE_TOOL_NAME) {
336
+ return typedEntry.input;
337
+ }
338
+ }
339
+ }
340
+ }
341
+ return parseStrictJsonValue(stripMarkdownCodeFence(extractAnthropicText(payload)), "The built-in Anthropic mining provider returned invalid JSON.");
342
+ }
238
343
  export async function generateMiningSentences(request, options) {
239
344
  const config = await loadClientConfig({
240
345
  path: options.paths.clientConfigPath,
@@ -30,6 +30,7 @@ export interface MiningFollowVisualizerState {
30
30
  settledBoardEntries: MiningSentenceBoardEntry[];
31
31
  provisionalRequiredWords: readonly string[];
32
32
  provisionalEntry: MiningProvisionalSentenceEntry;
33
+ provisionalBroadcastTxid: string | null;
33
34
  latestSentence: string | null;
34
35
  latestTxid: string | null;
35
36
  recentWin: MiningRecentWinSummary | null;
@@ -133,6 +133,16 @@ function formatRequiredWordsLine(words) {
133
133
  }
134
134
  return `Required words: ${words.map((word) => word.toUpperCase()).join(", ")}`;
135
135
  }
136
+ function formatProvisionalTxLinkLine(entry, txid) {
137
+ if (entry.domainName === null || entry.sentence === null || txid === null) {
138
+ return "";
139
+ }
140
+ const normalizedTxid = normalizeInlineText(txid);
141
+ if (normalizedTxid.length === 0) {
142
+ return "";
143
+ }
144
+ return `View at: https://mempool.space/tx/${normalizedTxid}`;
145
+ }
136
146
  function formatProvisionalSentenceRow(entry, requiredWords) {
137
147
  if (entry.domainName === null || entry.sentence === null) {
138
148
  return ["", "", ""];
@@ -151,6 +161,7 @@ export function createEmptyMiningFollowVisualizerState() {
151
161
  domainName: null,
152
162
  sentence: null,
153
163
  },
164
+ provisionalBroadcastTxid: null,
154
165
  latestSentence: null,
155
166
  latestTxid: null,
156
167
  recentWin: null,
@@ -173,6 +184,7 @@ function cloneMiningFollowVisualizerState(state) {
173
184
  provisionalEntry: {
174
185
  ...state.provisionalEntry,
175
186
  },
187
+ provisionalBroadcastTxid: state.provisionalBroadcastTxid,
176
188
  recentWin: state.recentWin === null
177
189
  ? null
178
190
  : {
@@ -375,6 +387,7 @@ export class MiningFollowVisualizer {
375
387
  : formatSentenceRow(entry);
376
388
  }).flat(),
377
389
  "----------",
390
+ formatProvisionalTxLinkLine(uiState.provisionalEntry, uiState.provisionalBroadcastTxid),
378
391
  formatRequiredWordsLine(uiState.provisionalRequiredWords),
379
392
  ...formatProvisionalSentenceRow(uiState.provisionalEntry, uiState.provisionalRequiredWords),
380
393
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",