opencandle 0.10.0 → 0.11.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 +12 -9
- package/dist/analysts/orchestrator.d.ts +3 -4
- package/dist/analysts/orchestrator.js +34 -44
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli-main.js +242 -9
- package/dist/cli-main.js.map +1 -1
- package/dist/config.d.ts +4 -9
- package/dist/config.js +7 -10
- package/dist/config.js.map +1 -1
- package/dist/doctor/report.js +1 -1
- package/dist/doctor/report.js.map +1 -1
- package/dist/infra/cache.d.ts +6 -0
- package/dist/infra/cache.js +16 -6
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/freshness.d.ts +21 -0
- package/dist/infra/freshness.js +119 -0
- package/dist/infra/freshness.js.map +1 -0
- package/dist/infra/market-calendar.d.ts +14 -0
- package/dist/infra/market-calendar.js +137 -0
- package/dist/infra/market-calendar.js.map +1 -0
- package/dist/infra/rate-limiter.js +1 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/market-state/daily-report.d.ts +1 -1
- package/dist/market-state/daily-report.js +5 -16
- package/dist/market-state/daily-report.js.map +1 -1
- package/dist/market-state/service.d.ts +0 -35
- package/dist/market-state/service.js +0 -63
- package/dist/market-state/service.js.map +1 -1
- package/dist/market-state/summaries.d.ts +8 -0
- package/dist/market-state/summaries.js +70 -0
- package/dist/market-state/summaries.js.map +1 -0
- package/dist/memory/sqlite.js +20 -19
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/onboarding/providers.d.ts +25 -1
- package/dist/onboarding/providers.js +26 -0
- package/dist/onboarding/providers.js.map +1 -1
- package/dist/pi/opencandle-extension.js +57 -226
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session-action-dedupe.d.ts +6 -0
- package/dist/pi/session-action-dedupe.js +126 -0
- package/dist/pi/session-action-dedupe.js.map +1 -0
- package/dist/pi/session-writer-lock.d.ts +26 -2
- package/dist/pi/session-writer-lock.js +230 -18
- package/dist/pi/session-writer-lock.js.map +1 -1
- package/dist/pi/setup.js +5 -5
- package/dist/pi/setup.js.map +1 -1
- package/dist/pi/tui-session-coordinator.d.ts +15 -0
- package/dist/pi/tui-session-coordinator.js +283 -0
- package/dist/pi/tui-session-coordinator.js.map +1 -0
- package/dist/prompts/context-builder.js +1 -1
- package/dist/prompts/policy-cards.js +1 -1
- package/dist/prompts/policy-cards.js.map +1 -1
- package/dist/providers/alpha-vantage.js +6 -0
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +1 -0
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/polymarket.d.ts +2 -0
- package/dist/providers/polymarket.js +163 -0
- package/dist/providers/polymarket.js.map +1 -0
- package/dist/providers/wrap-provider.js +5 -3
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/providers/yahoo-finance.d.ts +3 -0
- package/dist/providers/yahoo-finance.js +206 -10
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.js +4 -5
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/routing/entity-extractor.js +16 -0
- package/dist/routing/entity-extractor.js.map +1 -1
- package/dist/routing/route-manifest.js +1 -2
- package/dist/routing/route-manifest.js.map +1 -1
- package/dist/routing/router-llm-client.js +18 -3
- package/dist/routing/router-llm-client.js.map +1 -1
- package/dist/routing/router-prompt.js +1 -1
- package/dist/routing/router.d.ts +1 -1
- package/dist/routing/router.js +309 -9
- package/dist/routing/router.js.map +1 -1
- package/dist/routing/slot-resolver.d.ts +1 -0
- package/dist/routing/slot-resolver.js +1 -1
- package/dist/routing/slot-resolver.js.map +1 -1
- package/dist/runtime/evidence.d.ts +1 -0
- package/dist/runtime/evidence.js.map +1 -1
- package/dist/runtime/numeric-claims.d.ts +23 -0
- package/dist/runtime/numeric-claims.js +100 -0
- package/dist/runtime/numeric-claims.js.map +1 -0
- package/dist/runtime/planning-evidence.js +1 -76
- package/dist/runtime/planning-evidence.js.map +1 -1
- package/dist/runtime/prompt-step.d.ts +10 -1
- package/dist/runtime/prompt-step.js +116 -2
- package/dist/runtime/prompt-step.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +11 -1
- package/dist/runtime/session-coordinator.js +354 -62
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/runtime/validation.d.ts +3 -1
- package/dist/runtime/workflow-events.d.ts +1 -1
- package/dist/runtime/workflow-events.js.map +1 -1
- package/dist/runtime/workflow-types.d.ts +3 -0
- package/dist/runtime/workflow-types.js.map +1 -1
- package/dist/system-prompt.js +1 -1
- package/dist/tools/fundamentals/comps.d.ts +1 -0
- package/dist/tools/fundamentals/comps.js +20 -9
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.d.ts +1 -1
- package/dist/tools/fundamentals/dcf.js +190 -76
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/macro/event-probabilities.d.ts +8 -0
- package/dist/tools/macro/event-probabilities.js +121 -0
- package/dist/tools/macro/event-probabilities.js.map +1 -0
- package/dist/tools/market/crypto-price.d.ts +4 -1
- package/dist/tools/market/crypto-price.js +10 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/screen-stocks.js +14 -3
- package/dist/tools/market/screen-stocks.js.map +1 -1
- package/dist/tools/market/stock-quote.d.ts +4 -1
- package/dist/tools/market/stock-quote.js +9 -4
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/option-chain.d.ts +4 -1
- package/dist/tools/options/option-chain.js +12 -1
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/daily-report.js +10 -4
- package/dist/tools/portfolio/daily-report.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.js +1 -1
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/technical/backtest.d.ts +23 -5
- package/dist/tools/technical/backtest.js +131 -94
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +1 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/fundamentals.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/market.d.ts +2 -0
- package/dist/types/options.d.ts +1 -0
- package/dist/types/prediction-markets.d.ts +13 -0
- package/dist/types/prediction-markets.js +2 -0
- package/dist/types/prediction-markets.js.map +1 -0
- package/gui/server/chat-event-adapter.ts +89 -5
- package/gui/server/http-routes.ts +661 -37
- package/gui/server/invoke-tool.ts +165 -16
- package/gui/server/live-chat-event-adapter.ts +37 -2
- package/gui/server/local-session-coordinator.ts +97 -0
- package/gui/server/market-state-api.ts +64 -48
- package/gui/server/projector.ts +132 -3
- package/gui/server/server.ts +51 -6
- package/gui/server/session-actions.ts +146 -45
- package/gui/server/session-entry-wait.ts +22 -3
- package/gui/server/tool-metadata.ts +3 -1
- package/gui/server/ws-hub.ts +77 -10
- package/gui/shared/chat-events.ts +10 -1
- package/gui/shared/event-reducer.ts +1 -0
- package/gui/web/dist/assets/CatalogOverlay-DZ1niyQm.js +1 -0
- package/gui/web/dist/assets/index-D4F9AJnn.css +2 -0
- package/gui/web/dist/assets/index-DtqMMBTr.js +65 -0
- package/gui/web/dist/index.html +2 -2
- package/package.json +13 -15
- package/src/analysts/orchestrator.ts +35 -56
- package/src/cli-main.ts +253 -13
- package/src/config.ts +12 -20
- package/src/doctor/report.ts +1 -1
- package/src/infra/cache.ts +24 -11
- package/src/infra/freshness.ts +165 -0
- package/src/infra/market-calendar.ts +193 -0
- package/src/infra/rate-limiter.ts +1 -0
- package/src/market-state/daily-report.ts +6 -16
- package/src/market-state/service.ts +0 -136
- package/src/market-state/summaries.ts +75 -0
- package/src/memory/sqlite.ts +23 -19
- package/src/onboarding/providers.ts +29 -1
- package/src/pi/opencandle-extension.ts +67 -270
- package/src/pi/session-action-dedupe.ts +155 -0
- package/src/pi/session-writer-lock.ts +290 -20
- package/src/pi/setup.ts +6 -6
- package/src/pi/tui-session-coordinator.ts +351 -0
- package/src/prompts/context-builder.ts +1 -1
- package/src/prompts/policy-cards.ts +1 -1
- package/src/providers/alpha-vantage.ts +6 -0
- package/src/providers/coingecko.ts +2 -0
- package/src/providers/polymarket.ts +214 -0
- package/src/providers/wrap-provider.ts +5 -3
- package/src/providers/yahoo-finance.ts +339 -27
- package/src/routing/classify-intent.ts +4 -5
- package/src/routing/entity-extractor.ts +17 -0
- package/src/routing/route-manifest.ts +1 -2
- package/src/routing/router-llm-client.ts +31 -19
- package/src/routing/router-prompt.ts +1 -1
- package/src/routing/router.ts +379 -8
- package/src/routing/slot-resolver.ts +1 -1
- package/src/runtime/evidence.ts +1 -0
- package/src/runtime/numeric-claims.ts +108 -0
- package/src/runtime/planning-evidence.ts +6 -91
- package/src/runtime/prompt-step.ts +127 -2
- package/src/runtime/session-coordinator.ts +464 -74
- package/src/runtime/validation.ts +1 -1
- package/src/runtime/workflow-events.ts +2 -0
- package/src/runtime/workflow-types.ts +3 -0
- package/src/system-prompt.ts +1 -1
- package/src/tools/AGENTS.md +1 -1
- package/src/tools/fundamentals/comps.ts +25 -14
- package/src/tools/fundamentals/dcf.ts +217 -87
- package/src/tools/index.ts +3 -3
- package/src/tools/macro/event-probabilities.ts +141 -0
- package/src/tools/market/crypto-price.ts +14 -2
- package/src/tools/market/screen-stocks.ts +14 -3
- package/src/tools/market/stock-quote.ts +49 -43
- package/src/tools/options/option-chain.ts +16 -2
- package/src/tools/portfolio/daily-report.ts +10 -4
- package/src/tools/portfolio/risk-analysis.ts +1 -1
- package/src/tools/technical/backtest.ts +167 -108
- package/src/tools/technical/indicators.ts +1 -1
- package/src/types/fundamentals.ts +1 -0
- package/src/types/index.ts +1 -0
- package/src/types/market.ts +2 -0
- package/src/types/options.ts +1 -0
- package/src/types/prediction-markets.ts +13 -0
- package/dist/tools/portfolio/predictions.d.ts +0 -55
- package/dist/tools/portfolio/predictions.js +0 -422
- package/dist/tools/portfolio/predictions.js.map +0 -1
- package/gui/server/chat-run-session.ts +0 -16
- package/gui/web/dist/assets/CatalogOverlay-CYptsda-.js +0 -1
- package/gui/web/dist/assets/index-B7QAjY5g.js +0 -65
- package/gui/web/dist/assets/index-D5dbWPfM.css +0 -2
- package/src/tools/portfolio/predictions.ts +0 -553
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { PortfolioLotRecord, ReportRunRecord, WatchlistItemRecord } from "./service.js";
|
|
2
|
+
|
|
3
|
+
export function formatPortfolioSummary(lots: PortfolioLotRecord[], limit = 8): string[] {
|
|
4
|
+
if (lots.length === 0) return [];
|
|
5
|
+
const lines = ["Portfolio lots:"];
|
|
6
|
+
for (const lot of lots.slice(0, limit)) {
|
|
7
|
+
const costBasis = formatMoney(lot.quantity * lot.avgCost, lot.currency);
|
|
8
|
+
const name = lot.name ? ` (${lot.name})` : "";
|
|
9
|
+
lines.push(
|
|
10
|
+
`- ${lot.symbol}: ${lot.quantity} @ ${formatMoney(lot.avgCost, lot.currency)}, cost basis ${costBasis}${name}`,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return lines;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatWatchlistSummary(items: WatchlistItemRecord[], limit = 8): string[] {
|
|
17
|
+
if (items.length === 0) return [];
|
|
18
|
+
const lines = ["Watchlist:"];
|
|
19
|
+
for (const item of items.slice(0, limit)) {
|
|
20
|
+
const parts = [
|
|
21
|
+
item.targetPrice == null
|
|
22
|
+
? null
|
|
23
|
+
: `target ${formatMoney(item.targetPrice, item.priceCurrency ?? item.currency ?? "USD")}`,
|
|
24
|
+
item.stopPrice == null
|
|
25
|
+
? null
|
|
26
|
+
: `stop ${formatMoney(item.stopPrice, item.priceCurrency ?? item.currency ?? "USD")}`,
|
|
27
|
+
item.thesis ? `thesis: ${item.thesis}` : null,
|
|
28
|
+
item.tags && item.tags.length > 0 ? `tags: ${item.tags.join(", ")}` : null,
|
|
29
|
+
item.notes ? `notes: ${item.notes}` : null,
|
|
30
|
+
].filter((part): part is string => part != null);
|
|
31
|
+
lines.push(
|
|
32
|
+
`- ${item.symbol}${item.name ? ` (${item.name})` : ""}${parts.length > 0 ? ` — ${parts.join("; ")}` : ""}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return lines;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function formatLatestReportSummary(
|
|
39
|
+
report: ReportRunRecord | undefined,
|
|
40
|
+
options: { includeSummary?: boolean } = {},
|
|
41
|
+
): string[] {
|
|
42
|
+
if (!report) return [];
|
|
43
|
+
const completed = report.completedAt ?? report.startedAt;
|
|
44
|
+
const summary = options.includeSummary === false ? "" : formatJsonSummary(report.summaryJson);
|
|
45
|
+
const lines = [
|
|
46
|
+
`Latest report run: ${report.status} at ${completed}${summary ? ` — ${summary}` : ""}`,
|
|
47
|
+
];
|
|
48
|
+
const reportText = options.includeSummary === false ? "" : extractReportText(report.summaryJson);
|
|
49
|
+
if (reportText) lines.push("Report text:", truncateText(reportText, 8_000));
|
|
50
|
+
return lines;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function formatMoney(value: number, currency: string): string {
|
|
54
|
+
const normalized = currency.toUpperCase();
|
|
55
|
+
if (normalized === "USD") return `$${value.toFixed(2)}`;
|
|
56
|
+
return `${normalized} ${value.toFixed(2)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function formatJsonSummary(value: unknown): string {
|
|
60
|
+
if (value == null) return "";
|
|
61
|
+
const json = JSON.stringify(value);
|
|
62
|
+
if (json.length <= 90) return json;
|
|
63
|
+
return `${json.slice(0, 87)}...`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractReportText(value: unknown): string {
|
|
67
|
+
if (!value || typeof value !== "object" || !("text" in value)) return "";
|
|
68
|
+
const text = (value as { text?: unknown }).text;
|
|
69
|
+
return typeof text === "string" ? text.trim() : "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function truncateText(value: string, maxLength: number): string {
|
|
73
|
+
if (value.length <= maxLength) return value;
|
|
74
|
+
return `${value.slice(0, maxLength - 3)}...`;
|
|
75
|
+
}
|
package/src/memory/sqlite.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname } from "node:path";
|
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
4
|
import { ensureOpenCandleHomeDir, getStateDbPath } from "../infra/opencandle-paths.js";
|
|
5
5
|
|
|
6
|
-
const CURRENT_SCHEMA_VERSION =
|
|
6
|
+
const CURRENT_SCHEMA_VERSION = 8;
|
|
7
7
|
|
|
8
8
|
const CURRENT_SCHEMA = `
|
|
9
9
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
@@ -178,23 +178,6 @@ const CURRENT_SCHEMA = `
|
|
|
178
178
|
ON portfolio_lots(source, source_lot_id)
|
|
179
179
|
WHERE source IS NOT NULL AND source_lot_id IS NOT NULL;
|
|
180
180
|
|
|
181
|
-
CREATE TABLE IF NOT EXISTS prediction_records (
|
|
182
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
183
|
-
instrument_id INTEGER NOT NULL,
|
|
184
|
-
direction TEXT NOT NULL,
|
|
185
|
-
conviction REAL NOT NULL,
|
|
186
|
-
entry_price REAL NOT NULL,
|
|
187
|
-
target_price REAL,
|
|
188
|
-
opened_at TEXT NOT NULL,
|
|
189
|
-
expires_at TEXT NOT NULL,
|
|
190
|
-
status TEXT NOT NULL,
|
|
191
|
-
resolved_at TEXT,
|
|
192
|
-
result_json TEXT,
|
|
193
|
-
created_at TEXT NOT NULL,
|
|
194
|
-
updated_at TEXT NOT NULL,
|
|
195
|
-
FOREIGN KEY (instrument_id) REFERENCES instruments(id) ON DELETE RESTRICT
|
|
196
|
-
);
|
|
197
|
-
|
|
198
181
|
CREATE TABLE IF NOT EXISTS alert_rules (
|
|
199
182
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
200
183
|
scope_type TEXT NOT NULL,
|
|
@@ -380,14 +363,21 @@ function ensureCurrentSchema(db: Database.Database): void {
|
|
|
380
363
|
return;
|
|
381
364
|
}
|
|
382
365
|
|
|
366
|
+
if (currentVersion === 7) {
|
|
367
|
+
migrateV7ToV8(db);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
383
371
|
if (currentVersion === 6) {
|
|
384
372
|
migrateV6ToV7(db);
|
|
373
|
+
migrateV7ToV8(db);
|
|
385
374
|
return;
|
|
386
375
|
}
|
|
387
376
|
|
|
388
377
|
if (currentVersion === 5) {
|
|
389
378
|
migrateV5ToV6(db);
|
|
390
379
|
migrateV6ToV7(db);
|
|
380
|
+
migrateV7ToV8(db);
|
|
391
381
|
return;
|
|
392
382
|
}
|
|
393
383
|
|
|
@@ -395,6 +385,7 @@ function ensureCurrentSchema(db: Database.Database): void {
|
|
|
395
385
|
migrateV4ToV5(db);
|
|
396
386
|
migrateV5ToV6(db);
|
|
397
387
|
migrateV6ToV7(db);
|
|
388
|
+
migrateV7ToV8(db);
|
|
398
389
|
return;
|
|
399
390
|
}
|
|
400
391
|
|
|
@@ -403,16 +394,19 @@ function ensureCurrentSchema(db: Database.Database): void {
|
|
|
403
394
|
migrateV4ToV5(db);
|
|
404
395
|
migrateV5ToV6(db);
|
|
405
396
|
migrateV6ToV7(db);
|
|
397
|
+
migrateV7ToV8(db);
|
|
406
398
|
return;
|
|
407
399
|
}
|
|
408
400
|
|
|
409
|
-
// Additive v2 → v3 →
|
|
401
|
+
// Additive v2 → v3 → ... → v7 migration without dropping data (v8 drops
|
|
402
|
+
// prediction_records as the explicit removal of the predictions feature).
|
|
410
403
|
if (currentVersion === 2) {
|
|
411
404
|
migrateV2ToV3(db);
|
|
412
405
|
migrateV3ToV4(db);
|
|
413
406
|
migrateV4ToV5(db);
|
|
414
407
|
migrateV5ToV6(db);
|
|
415
408
|
migrateV6ToV7(db);
|
|
409
|
+
migrateV7ToV8(db);
|
|
416
410
|
return;
|
|
417
411
|
}
|
|
418
412
|
|
|
@@ -487,6 +481,16 @@ function migrateV6ToV7(db: Database.Database): void {
|
|
|
487
481
|
|
|
488
482
|
db.exec(CURRENT_SCHEMA);
|
|
489
483
|
|
|
484
|
+
db.prepare("DELETE FROM schema_version").run();
|
|
485
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(7);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function migrateV7ToV8(db: Database.Database): void {
|
|
489
|
+
// Predictions feature removal: dropping prediction_records is the explicit,
|
|
490
|
+
// documented destructive step for this table; all other rows are preserved.
|
|
491
|
+
db.exec("DROP TABLE IF EXISTS prediction_records");
|
|
492
|
+
db.exec(CURRENT_SCHEMA);
|
|
493
|
+
|
|
490
494
|
db.prepare("DELETE FROM schema_version").run();
|
|
491
495
|
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(CURRENT_SCHEMA_VERSION);
|
|
492
496
|
}
|
|
@@ -12,7 +12,7 @@ import { getConfig, loadFileConfig } from "../config.js";
|
|
|
12
12
|
|
|
13
13
|
export type ApiKeyProviderId = "alpha_vantage" | "fred" | "finnhub" | "brave" | "exa";
|
|
14
14
|
export type ExternalToolProviderId = "twitter" | "reddit";
|
|
15
|
-
export type PublicHttpProviderId = "yahoo";
|
|
15
|
+
export type PublicHttpProviderId = "polymarket" | "tradingview" | "yahoo";
|
|
16
16
|
export type ProviderId = ApiKeyProviderId | ExternalToolProviderId | PublicHttpProviderId;
|
|
17
17
|
|
|
18
18
|
export type ProviderCategory =
|
|
@@ -201,6 +201,34 @@ export const PROVIDERS = [
|
|
|
201
201
|
snoozeDurationDays: 7,
|
|
202
202
|
instructionsHint: "No account needed; OpenCandle checks public Yahoo Finance reachability",
|
|
203
203
|
},
|
|
204
|
+
{
|
|
205
|
+
id: "polymarket",
|
|
206
|
+
kind: "public-http",
|
|
207
|
+
displayName: "Polymarket",
|
|
208
|
+
category: "macro",
|
|
209
|
+
tier: "hard",
|
|
210
|
+
aliases: ["polymarket", "prediction-markets", "event-probabilities"],
|
|
211
|
+
probeUrl: "https://gamma-api.polymarket.com/public-search?q=fed%20rate%20cut&limit=1",
|
|
212
|
+
unlocks: ["market-implied event probabilities", "prediction-market resolution criteria"],
|
|
213
|
+
fallbackDescription: null,
|
|
214
|
+
snoozeDurationDays: 7,
|
|
215
|
+
instructionsHint: "No account needed; OpenCandle checks public Polymarket Gamma reachability",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: "tradingview",
|
|
219
|
+
kind: "public-http",
|
|
220
|
+
displayName: "TradingView Scanner",
|
|
221
|
+
category: "market",
|
|
222
|
+
tier: "soft",
|
|
223
|
+
aliases: ["tradingview", "tradingview-scanner", "screener"],
|
|
224
|
+
probeUrl: "https://scanner.tradingview.com/america/scan2?label-product=screener-stock",
|
|
225
|
+
unlocks: ["stock screens", "ticker search fallback", "watchlist quote fallback"],
|
|
226
|
+
fallbackDescription:
|
|
227
|
+
"Core market quote/history tools continue through Yahoo and Alpha Vantage when TradingView scanner is unavailable",
|
|
228
|
+
snoozeDurationDays: 7,
|
|
229
|
+
instructionsHint:
|
|
230
|
+
"No account needed; OpenCandle checks the public TradingView scanner endpoint",
|
|
231
|
+
},
|
|
204
232
|
{
|
|
205
233
|
id: "twitter",
|
|
206
234
|
kind: "external-tool",
|
|
@@ -25,15 +25,12 @@ import { formatPreflightDropAnnotation, preflightSymbols } from "../prompts/symb
|
|
|
25
25
|
import { buildAssumptionsBlockFromRouter } from "../prompts/workflow-prompts.js";
|
|
26
26
|
import {
|
|
27
27
|
buildResolvedTurnContext,
|
|
28
|
-
classifyWithLegacyRules,
|
|
29
28
|
createPiAiRouterClient,
|
|
30
|
-
hasFinanceSignals,
|
|
31
29
|
resolveOptionsScreenerSlots,
|
|
32
30
|
resolvePortfolioSlots,
|
|
33
31
|
route as routeLlm,
|
|
34
32
|
} from "../routing/index.js";
|
|
35
33
|
import type { RouterInputContext, RouterLlmClient, RouterOutput } from "../routing/router-types.js";
|
|
36
|
-
import { disambiguateSymbols } from "../routing/symbol-disambiguator.js";
|
|
37
34
|
import type { ResolvedTurnContext } from "../routing/turn-context.js";
|
|
38
35
|
import type {
|
|
39
36
|
CompareAssetsSlots,
|
|
@@ -68,6 +65,10 @@ export interface OpenCandleExtensionOptions {
|
|
|
68
65
|
titleCompletion?: (prompt: string) => Promise<string>;
|
|
69
66
|
}
|
|
70
67
|
|
|
68
|
+
interface OriginalInputMarkerContext {
|
|
69
|
+
sessionManager?: { getBranch?: () => unknown[]; getEntries?: () => unknown[] };
|
|
70
|
+
}
|
|
71
|
+
|
|
71
72
|
export default function openCandleExtension(
|
|
72
73
|
pi: ExtensionAPI,
|
|
73
74
|
options?: OpenCandleExtensionOptions,
|
|
@@ -76,10 +77,22 @@ export default function openCandleExtension(
|
|
|
76
77
|
|
|
77
78
|
// Workflow transforms replace the user's turn with the expanded prompt; this
|
|
78
79
|
// marker lets the GUI render the user's original words instead.
|
|
79
|
-
const markOriginalInput = (original: string): void => {
|
|
80
|
+
const markOriginalInput = (original: string, ctx?: OriginalInputMarkerContext): void => {
|
|
81
|
+
if (hasUnconsumedOriginalInputMarker(ctx)) return;
|
|
80
82
|
pi.appendEntry("opencandle-user-input", { original });
|
|
81
83
|
};
|
|
82
84
|
|
|
85
|
+
const appendComprehensiveAnalysisWorkflowEntry = (
|
|
86
|
+
symbol: string,
|
|
87
|
+
definition: ReturnType<typeof buildComprehensiveAnalysisDefinition>,
|
|
88
|
+
): void => {
|
|
89
|
+
pi.appendEntry("opencandle-workflow", {
|
|
90
|
+
workflow: "comprehensive_analysis",
|
|
91
|
+
resolvedSlots: { symbol },
|
|
92
|
+
analystsTotal: definition.steps.filter((step) => step.stepType.startsWith("analyst_")).length,
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
83
96
|
// Credential-interception state. Lifetime:
|
|
84
97
|
// `sessionPromptedSet` — cleared on session_start, persists across turns
|
|
85
98
|
// within a session so users don't get re-prompted after picking
|
|
@@ -118,9 +131,8 @@ export default function openCandleExtension(
|
|
|
118
131
|
ctx.ui.notify("Usage: /analyze <ticker>", "warning");
|
|
119
132
|
return;
|
|
120
133
|
}
|
|
121
|
-
const definition = buildComprehensiveAnalysisDefinition(symbol
|
|
122
|
-
|
|
123
|
-
});
|
|
134
|
+
const definition = buildComprehensiveAnalysisDefinition(symbol);
|
|
135
|
+
appendComprehensiveAnalysisWorkflowEntry(symbol, definition);
|
|
124
136
|
coordinator.executeWorkflow(pi, definition, ctx);
|
|
125
137
|
},
|
|
126
138
|
});
|
|
@@ -674,244 +686,27 @@ export default function openCandleExtension(
|
|
|
674
686
|
return undefined;
|
|
675
687
|
});
|
|
676
688
|
|
|
677
|
-
// Input handling —
|
|
689
|
+
// Input handling — the LLM router is the single production routing path.
|
|
678
690
|
pi.on("input", async (event, ctx) => {
|
|
679
691
|
if (event.source === "extension") return;
|
|
680
692
|
coordinator.clearTickerValidationCache();
|
|
681
693
|
|
|
682
|
-
// Check for comprehensive analysis pattern
|
|
694
|
+
// Check for comprehensive analysis pattern before routing.
|
|
683
695
|
const analysis = isAnalysisRequest(event.text);
|
|
684
696
|
if (analysis.match && analysis.symbol) {
|
|
685
|
-
const definition = buildComprehensiveAnalysisDefinition(analysis.symbol
|
|
686
|
-
|
|
687
|
-
});
|
|
688
|
-
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
689
|
-
if (prompt) markOriginalInput(event.text);
|
|
690
|
-
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
const mode = getConfig().routerMode;
|
|
694
|
-
if (mode === "llm") {
|
|
695
|
-
const dispatched = await handleLlmRouterTurn(event.text, ctx);
|
|
696
|
-
// Dispatched a workflow → the original user turn is now represented by
|
|
697
|
-
// the workflow's queued prompts; tell Pi not to also forward it.
|
|
698
|
-
// Fallback path (no dispatch) → let Pi pass the user turn through to the
|
|
699
|
-
// main agent, which will run under the router-supplied fallback context.
|
|
700
|
-
return dispatched || undefined;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// --- explicit legacy rules mode (`OPENCANDLE_ROUTER_MODE=rules`) ---
|
|
704
|
-
// Extract and persist user preferences (legacy regex path)
|
|
705
|
-
coordinator.extractAndStorePreferences(event.text);
|
|
706
|
-
const storage = coordinator.getStorage();
|
|
707
|
-
const workflowPrefs = storage?.getWorkflowPreferences("global") ?? {};
|
|
708
|
-
|
|
709
|
-
// Classify intent
|
|
710
|
-
let classification = classifyWithLegacyRules(event.text);
|
|
711
|
-
const ruleModeDisambiguation = disambiguateRulesModeSymbols(
|
|
712
|
-
event.text,
|
|
713
|
-
classification.entities.symbols,
|
|
714
|
-
);
|
|
715
|
-
appendSymbolDropEntries(ruleModeDisambiguation.dropped, "rules");
|
|
716
|
-
classification = {
|
|
717
|
-
...classification,
|
|
718
|
-
entities: {
|
|
719
|
-
...classification.entities,
|
|
720
|
-
symbols: ruleModeDisambiguation.kept,
|
|
721
|
-
},
|
|
722
|
-
};
|
|
723
|
-
if (
|
|
724
|
-
isComparePrompt(event.text) &&
|
|
725
|
-
ruleModeDisambiguation.dropped.length > 0 &&
|
|
726
|
-
classification.entities.symbols.length < 2
|
|
727
|
-
) {
|
|
728
|
-
pi.appendEntry("opencandle-workflow-aborted", {
|
|
729
|
-
reason: "symbol-disambiguation-insufficient-symbols",
|
|
730
|
-
dropped: ruleModeDisambiguation.dropped,
|
|
731
|
-
validSymbols: classification.entities.symbols,
|
|
732
|
-
});
|
|
733
|
-
const base = coordinator.buildRouterContextBase(ctx.sessionManager);
|
|
734
|
-
const output: RouterOutput = {
|
|
735
|
-
routeKind: "clarification",
|
|
736
|
-
route: "fallback",
|
|
737
|
-
workflow: "compare_assets",
|
|
738
|
-
entities: classification.entities,
|
|
739
|
-
slots: {},
|
|
740
|
-
preference_updates: [],
|
|
741
|
-
missing_required: ["symbols"],
|
|
742
|
-
tool_bundles: ["clarification"],
|
|
743
|
-
diagnostics: ruleModeDisambiguation.dropped.map((drop) => ({
|
|
744
|
-
code: "symbol_dropped",
|
|
745
|
-
message: `${drop.token} dropped: ${drop.reason}`,
|
|
746
|
-
details: {
|
|
747
|
-
token: drop.token,
|
|
748
|
-
reason: drop.reason,
|
|
749
|
-
signalsChecked: drop.signalsChecked,
|
|
750
|
-
source: "rules",
|
|
751
|
-
},
|
|
752
|
-
})),
|
|
753
|
-
reasoning: "rules-mode acronym disambiguation left fewer than two symbols for comparison",
|
|
754
|
-
};
|
|
755
|
-
const resolvedTurnContext = buildResolvedTurnContext({ text: event.text, ...base }, output, {
|
|
756
|
-
availableToolNames: safeGetAllToolNames(),
|
|
757
|
-
planning: {
|
|
758
|
-
migrationStatuses: getConfig().planningMigrationStatuses,
|
|
759
|
-
},
|
|
760
|
-
});
|
|
761
|
-
coordinator.setPendingResolvedTurnContext({
|
|
762
|
-
...resolvedTurnContext,
|
|
763
|
-
diagnostics: [
|
|
764
|
-
...resolvedTurnContext.diagnostics,
|
|
765
|
-
{
|
|
766
|
-
code: "compare_workflow_aborted",
|
|
767
|
-
message:
|
|
768
|
-
"compare workflow needs at least two validated symbols after acronym disambiguation",
|
|
769
|
-
},
|
|
770
|
-
],
|
|
771
|
-
});
|
|
772
|
-
coordinator.setPendingFallbackContext({
|
|
773
|
-
assumptionsBlock: [
|
|
774
|
-
"Assumptions Context:",
|
|
775
|
-
classification.entities.symbols.length > 0
|
|
776
|
-
? ` valid symbols: ${classification.entities.symbols.join(", ")} (user)`
|
|
777
|
-
: " valid symbols: (none)",
|
|
778
|
-
` dropped ambiguous ticker-like tokens: ${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")} (no positive ticker signal)`,
|
|
779
|
-
].join("\n"),
|
|
780
|
-
missingRequired: ["symbols"],
|
|
781
|
-
extraContext:
|
|
782
|
-
"Dropped ambiguous ticker-like tokens: " +
|
|
783
|
-
`${ruleModeDisambiguation.dropped.map((d) => d.token).join(", ")}. ` +
|
|
784
|
-
"Ask the user which ticker symbols they want compared before calling comparison tools.",
|
|
785
|
-
});
|
|
786
|
-
applyRouteToolScope(resolvedTurnContext);
|
|
787
|
-
return undefined;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
if (classification.workflow === "portfolio_builder") {
|
|
791
|
-
const resolution = resolvePortfolioSlots(classification.entities, workflowPrefs);
|
|
792
|
-
coordinator.recordWorkflowRun(
|
|
793
|
-
"portfolio_builder",
|
|
794
|
-
classification.entities,
|
|
795
|
-
resolution.resolved,
|
|
796
|
-
resolution.defaultsUsed,
|
|
797
|
-
);
|
|
798
|
-
pi.appendEntry("opencandle-workflow", {
|
|
799
|
-
workflow: "portfolio_builder",
|
|
800
|
-
entities: classification.entities,
|
|
801
|
-
resolved: resolution.resolved,
|
|
802
|
-
});
|
|
803
|
-
const definition = buildPortfolioWorkflowDefinition(resolution);
|
|
697
|
+
const definition = buildComprehensiveAnalysisDefinition(analysis.symbol);
|
|
698
|
+
appendComprehensiveAnalysisWorkflowEntry(analysis.symbol, definition);
|
|
804
699
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
805
|
-
if (prompt) markOriginalInput(event.text);
|
|
700
|
+
if (prompt) markOriginalInput(event.text, ctx);
|
|
806
701
|
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
807
702
|
}
|
|
808
703
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
resolution.resolved,
|
|
816
|
-
resolution.defaultsUsed,
|
|
817
|
-
);
|
|
818
|
-
pi.appendEntry("opencandle-workflow", {
|
|
819
|
-
workflow: "options_screener",
|
|
820
|
-
entities: classification.entities,
|
|
821
|
-
resolved: resolution.resolved,
|
|
822
|
-
});
|
|
823
|
-
const definition = buildOptionsScreenerWorkflowDefinition(resolution);
|
|
824
|
-
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
825
|
-
if (prompt) markOriginalInput(event.text);
|
|
826
|
-
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
if (
|
|
831
|
-
classification.workflow === "compare_assets" &&
|
|
832
|
-
classification.entities.symbols.length >= 2
|
|
833
|
-
) {
|
|
834
|
-
const resolution: SlotResolution<CompareAssetsSlots> = {
|
|
835
|
-
resolved: {
|
|
836
|
-
symbols: classification.entities.symbols,
|
|
837
|
-
metrics: classification.entities.compareMetrics,
|
|
838
|
-
timeHorizon: classification.entities.timeHorizon,
|
|
839
|
-
budget: classification.entities.budget,
|
|
840
|
-
assetScope: classification.entities.assetScope,
|
|
841
|
-
},
|
|
842
|
-
sources: {
|
|
843
|
-
symbols: "user",
|
|
844
|
-
...(classification.entities.timeHorizon ? { timeHorizon: "user" as const } : {}),
|
|
845
|
-
...(classification.entities.compareMetrics ? { metrics: "user" as const } : {}),
|
|
846
|
-
...(classification.entities.budget !== undefined ? { budget: "user" as const } : {}),
|
|
847
|
-
...(classification.entities.assetScope ? { assetScope: "user" as const } : {}),
|
|
848
|
-
},
|
|
849
|
-
defaultsUsed: [],
|
|
850
|
-
missingRequired: [],
|
|
851
|
-
};
|
|
852
|
-
coordinator.recordWorkflowRun(
|
|
853
|
-
"compare_assets",
|
|
854
|
-
classification.entities,
|
|
855
|
-
resolution.resolved,
|
|
856
|
-
resolution.defaultsUsed,
|
|
857
|
-
);
|
|
858
|
-
pi.appendEntry("opencandle-workflow", {
|
|
859
|
-
workflow: "compare_assets",
|
|
860
|
-
symbols: classification.entities.symbols,
|
|
861
|
-
});
|
|
862
|
-
const preflight = await preflightCompareResolution(resolution);
|
|
863
|
-
if (!preflight) {
|
|
864
|
-
coordinator.recordWorkflowRun(
|
|
865
|
-
"fallback",
|
|
866
|
-
classification.entities,
|
|
867
|
-
resolution.resolved,
|
|
868
|
-
[],
|
|
869
|
-
"clarification",
|
|
870
|
-
);
|
|
871
|
-
coordinator.setPendingFallbackContext({
|
|
872
|
-
assumptionsBlock: [
|
|
873
|
-
"Assumptions Context:",
|
|
874
|
-
` original symbols: ${classification.entities.symbols.join(", ")} (user)`,
|
|
875
|
-
].join("\n"),
|
|
876
|
-
missingRequired: ["symbols"],
|
|
877
|
-
extraContext:
|
|
878
|
-
"Compare workflow aborted because ticker preflight left fewer than two valid symbols. Ask the user to clarify the intended tickers before calling comparison tools.",
|
|
879
|
-
});
|
|
880
|
-
return undefined;
|
|
881
|
-
}
|
|
882
|
-
const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
|
|
883
|
-
applyPreflightAnnotation(definition, preflight.dropped);
|
|
884
|
-
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
885
|
-
if (prompt) markOriginalInput(event.text);
|
|
886
|
-
return prompt ? { action: "transform", text: prompt } : { action: "handled" };
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Rules-mode finance fallback: no workflow dispatched, but the turn is
|
|
890
|
-
// finance-shaped (classified finance intent, extracted symbols, or finance
|
|
891
|
-
// vocabulary). Record the fallback turn and stash a fallback context so
|
|
892
|
-
// the system prompt carries saved market state for this turn; non-finance
|
|
893
|
-
// prompts stay untouched.
|
|
894
|
-
const isFinanceFallback =
|
|
895
|
-
classification.workflow !== "unclassified" ||
|
|
896
|
-
classification.entities.symbols.length > 0 ||
|
|
897
|
-
hasFinanceSignals(event.text);
|
|
898
|
-
if (isFinanceFallback) {
|
|
899
|
-
coordinator.recordWorkflowRun("fallback", classification.entities, {}, [], "agent_task");
|
|
900
|
-
coordinator.setPendingFallbackContext({
|
|
901
|
-
assumptionsBlock: "",
|
|
902
|
-
missingRequired: [],
|
|
903
|
-
extraContext:
|
|
904
|
-
classification.entities.symbols.length > 0
|
|
905
|
-
? `Rules-router extracted symbols: ${classification.entities.symbols.join(", ")}.`
|
|
906
|
-
: undefined,
|
|
907
|
-
});
|
|
908
|
-
pi.appendEntry("opencandle-fallback-context", {
|
|
909
|
-
mode: "rules",
|
|
910
|
-
classifiedWorkflow: classification.workflow,
|
|
911
|
-
symbols: classification.entities.symbols,
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
return undefined;
|
|
704
|
+
const dispatched = await handleLlmRouterTurn(event.text, ctx);
|
|
705
|
+
// Dispatched a workflow → the original user turn is now represented by
|
|
706
|
+
// the workflow's queued prompts; tell Pi not to also forward it.
|
|
707
|
+
// Fallback path (no dispatch) → let Pi pass the user turn through to the
|
|
708
|
+
// main agent, which will run under the router-supplied fallback context.
|
|
709
|
+
return dispatched || undefined;
|
|
915
710
|
});
|
|
916
711
|
|
|
917
712
|
/**
|
|
@@ -1067,7 +862,7 @@ export default function openCandleExtension(
|
|
|
1067
862
|
});
|
|
1068
863
|
const definition = buildPortfolioWorkflowDefinition(resolution);
|
|
1069
864
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
1070
|
-
if (prompt) markOriginalInput(originalText);
|
|
865
|
+
if (prompt) markOriginalInput(originalText, ctx);
|
|
1071
866
|
return prompt ? { action: "transform", text: prompt } : false;
|
|
1072
867
|
}
|
|
1073
868
|
if (workflow === "options_screener") {
|
|
@@ -1092,7 +887,7 @@ export default function openCandleExtension(
|
|
|
1092
887
|
});
|
|
1093
888
|
const definition = buildOptionsScreenerWorkflowDefinition(resolution);
|
|
1094
889
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
1095
|
-
if (prompt) markOriginalInput(originalText);
|
|
890
|
+
if (prompt) markOriginalInput(originalText, ctx);
|
|
1096
891
|
return prompt ? { action: "transform", text: prompt } : false;
|
|
1097
892
|
}
|
|
1098
893
|
// Missing required symbol — treat as fallback with ask_user directive.
|
|
@@ -1150,7 +945,7 @@ export default function openCandleExtension(
|
|
|
1150
945
|
const definition = buildCompareAssetsWorkflowDefinition(preflight.resolution);
|
|
1151
946
|
applyPreflightAnnotation(definition, preflight.dropped);
|
|
1152
947
|
const prompt = coordinator.transformWorkflowInput(pi, definition, ctx);
|
|
1153
|
-
if (prompt) markOriginalInput(originalText);
|
|
948
|
+
if (prompt) markOriginalInput(originalText, ctx);
|
|
1154
949
|
return prompt ? { action: "transform", text: prompt } : false;
|
|
1155
950
|
}
|
|
1156
951
|
|
|
@@ -1206,36 +1001,6 @@ export default function openCandleExtension(
|
|
|
1206
1001
|
}
|
|
1207
1002
|
}
|
|
1208
1003
|
|
|
1209
|
-
function disambiguateRulesModeSymbols(
|
|
1210
|
-
text: string,
|
|
1211
|
-
extractedSymbols: string[],
|
|
1212
|
-
): {
|
|
1213
|
-
kept: string[];
|
|
1214
|
-
dropped: Array<{ token: string; reason: string; signalsChecked: string[] }>;
|
|
1215
|
-
} {
|
|
1216
|
-
const candidates = mergeSymbols(extractedSymbols, rawTickerLikeTokens(text));
|
|
1217
|
-
const disambiguated = disambiguateSymbols(candidates, text);
|
|
1218
|
-
return {
|
|
1219
|
-
kept: disambiguated.kept.filter((symbol) => extractedSymbols.includes(symbol)),
|
|
1220
|
-
dropped: disambiguated.dropped,
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
function rawTickerLikeTokens(text: string): string[] {
|
|
1225
|
-
const tokens: string[] = [];
|
|
1226
|
-
for (const match of text.matchAll(/\$?([A-Za-z]{1,5})\b/g)) {
|
|
1227
|
-
const raw = match[1];
|
|
1228
|
-
if (raw !== raw.toUpperCase()) continue;
|
|
1229
|
-
const token = raw.toUpperCase();
|
|
1230
|
-
if (!tokens.includes(token)) tokens.push(token);
|
|
1231
|
-
}
|
|
1232
|
-
return tokens;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
function isComparePrompt(text: string): boolean {
|
|
1236
|
-
return /\b(?:compare|vs\.?|versus|which\s+is\s+better)\b/i.test(text);
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
1004
|
async function preflightCompareResolution(
|
|
1240
1005
|
resolution: SlotResolution<CompareAssetsSlots>,
|
|
1241
1006
|
): Promise<{
|
|
@@ -1436,8 +1201,8 @@ export default function openCandleExtension(
|
|
|
1436
1201
|
ctx: Parameters<Parameters<ExtensionAPI["on"]>[1]>[1],
|
|
1437
1202
|
): RouterLlmClient | null {
|
|
1438
1203
|
// `ctx.model` is the currently selected pi-ai model. When unset (no auth
|
|
1439
|
-
// configured yet), we skip the router and the main agent
|
|
1440
|
-
//
|
|
1204
|
+
// configured yet), we skip the router and the main agent runs with its
|
|
1205
|
+
// default unrouted flow for the turn.
|
|
1441
1206
|
const model = (ctx as { model?: unknown }).model;
|
|
1442
1207
|
if (!model) return null;
|
|
1443
1208
|
// biome-ignore lint/suspicious/noExplicitAny: Pi typings keep Model generic
|
|
@@ -1460,6 +1225,38 @@ export default function openCandleExtension(
|
|
|
1460
1225
|
});
|
|
1461
1226
|
}
|
|
1462
1227
|
|
|
1228
|
+
function hasUnconsumedOriginalInputMarker(ctx?: OriginalInputMarkerContext): boolean {
|
|
1229
|
+
const manager = ctx?.sessionManager;
|
|
1230
|
+
const entries =
|
|
1231
|
+
typeof manager?.getBranch === "function"
|
|
1232
|
+
? manager.getBranch()
|
|
1233
|
+
: typeof manager?.getEntries === "function"
|
|
1234
|
+
? manager.getEntries()
|
|
1235
|
+
: [];
|
|
1236
|
+
let markerAfterLastUser = false;
|
|
1237
|
+
for (const entry of entries) {
|
|
1238
|
+
if (!isSessionEntryRecord(entry)) continue;
|
|
1239
|
+
if (entry.type === "message" && entry.message?.role === "user") {
|
|
1240
|
+
markerAfterLastUser = false;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
if (entry.type === "custom" && entry.customType === "opencandle-user-input") {
|
|
1244
|
+
markerAfterLastUser = true;
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (markerAfterLastUser) {
|
|
1248
|
+
markerAfterLastUser = false;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return markerAfterLastUser;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function isSessionEntryRecord(
|
|
1255
|
+
entry: unknown,
|
|
1256
|
+
): entry is { type?: unknown; customType?: unknown; message?: { role?: unknown } } {
|
|
1257
|
+
return typeof entry === "object" && entry !== null;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1463
1260
|
/** Concatenate text from a Pi message `content` (plain string or block array). */
|
|
1464
1261
|
function extractMessageText(content: unknown): string {
|
|
1465
1262
|
if (typeof content === "string") return content;
|