opencandle 0.2.0 → 0.3.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/README.md +110 -87
- package/dist/analysts/orchestrator.js +1 -2
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/config.d.ts +25 -5
- package/dist/config.js +16 -8
- package/dist/config.js.map +1 -1
- package/dist/infra/cache.d.ts +4 -0
- package/dist/infra/cache.js +4 -0
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/rate-limiter.js +6 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/onboarding/connect.d.ts +23 -0
- package/dist/onboarding/connect.js +107 -0
- package/dist/onboarding/connect.js.map +1 -0
- package/dist/onboarding/credential-interceptor.d.ts +44 -0
- package/dist/onboarding/credential-interceptor.js +72 -0
- package/dist/onboarding/credential-interceptor.js.map +1 -0
- package/dist/onboarding/degradation-accumulator.d.ts +21 -0
- package/dist/onboarding/degradation-accumulator.js +55 -0
- package/dist/onboarding/degradation-accumulator.js.map +1 -0
- package/dist/onboarding/prompt-user.d.ts +23 -0
- package/dist/onboarding/prompt-user.js +61 -0
- package/dist/onboarding/prompt-user.js.map +1 -0
- package/dist/onboarding/providers.d.ts +109 -0
- package/dist/onboarding/providers.js +236 -0
- package/dist/onboarding/providers.js.map +1 -0
- package/dist/onboarding/state.d.ts +31 -2
- package/dist/onboarding/state.js +141 -13
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.d.ts +34 -0
- package/dist/onboarding/tool-helpers.js +80 -0
- package/dist/onboarding/tool-helpers.js.map +1 -0
- package/dist/onboarding/tool-tags.d.ts +37 -0
- package/dist/onboarding/tool-tags.js +149 -0
- package/dist/onboarding/tool-tags.js.map +1 -0
- package/dist/onboarding/validation.d.ts +19 -0
- package/dist/onboarding/validation.js +117 -0
- package/dist/onboarding/validation.js.map +1 -0
- package/dist/pi/opencandle-extension.js +303 -4
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/setup.d.ts +0 -1
- package/dist/pi/setup.js +66 -119
- package/dist/pi/setup.js.map +1 -1
- package/dist/prompts/context-builder.js +2 -1
- package/dist/prompts/context-builder.js.map +1 -1
- package/dist/providers/alpha-vantage.js +20 -1
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/exa-search.d.ts +39 -0
- package/dist/providers/exa-search.js +276 -0
- package/dist/providers/exa-search.js.map +1 -0
- package/dist/providers/finnhub.d.ts +17 -0
- package/dist/providers/finnhub.js +94 -0
- package/dist/providers/finnhub.js.map +1 -0
- package/dist/providers/fred.js +13 -1
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/provider-credential-error.d.ts +8 -0
- package/dist/providers/provider-credential-error.js +22 -0
- package/dist/providers/provider-credential-error.js.map +1 -0
- package/dist/providers/reddit.d.ts +8 -0
- package/dist/providers/reddit.js +36 -9
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/twitter.js +2 -8
- package/dist/providers/twitter.js.map +1 -1
- package/dist/providers/web-search.d.ts +17 -0
- package/dist/providers/web-search.js +224 -0
- package/dist/providers/web-search.js.map +1 -0
- package/dist/providers/wrap-provider.d.ts +7 -0
- package/dist/providers/wrap-provider.js +15 -0
- package/dist/providers/wrap-provider.js.map +1 -1
- package/dist/routing/classify-intent.js +22 -0
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/runtime/session-coordinator.d.ts +0 -1
- package/dist/runtime/session-coordinator.js.map +1 -1
- package/dist/sentiment/adapters/finnhub.d.ts +7 -0
- package/dist/sentiment/adapters/finnhub.js +39 -0
- package/dist/sentiment/adapters/finnhub.js.map +1 -0
- package/dist/sentiment/adapters/reddit.d.ts +11 -0
- package/dist/sentiment/adapters/reddit.js +54 -0
- package/dist/sentiment/adapters/reddit.js.map +1 -0
- package/dist/sentiment/adapters/twitter.d.ts +9 -0
- package/dist/sentiment/adapters/twitter.js +32 -0
- package/dist/sentiment/adapters/twitter.js.map +1 -0
- package/dist/sentiment/adapters/web.d.ts +9 -0
- package/dist/sentiment/adapters/web.js +40 -0
- package/dist/sentiment/adapters/web.js.map +1 -0
- package/dist/sentiment/index.d.ts +16 -0
- package/dist/sentiment/index.js +44 -0
- package/dist/sentiment/index.js.map +1 -0
- package/dist/sentiment/keywords.d.ts +2 -0
- package/dist/sentiment/keywords.js +9 -0
- package/dist/sentiment/keywords.js.map +1 -0
- package/dist/sentiment/pipeline.d.ts +9 -0
- package/dist/sentiment/pipeline.js +57 -0
- package/dist/sentiment/pipeline.js.map +1 -0
- package/dist/sentiment/scorer.d.ts +9 -0
- package/dist/sentiment/scorer.js +64 -0
- package/dist/sentiment/scorer.js.map +1 -0
- package/dist/sentiment/store.d.ts +24 -0
- package/dist/sentiment/store.js +177 -0
- package/dist/sentiment/store.js.map +1 -0
- package/dist/sentiment/trends.d.ts +13 -0
- package/dist/sentiment/trends.js +73 -0
- package/dist/sentiment/trends.js.map +1 -0
- package/dist/sentiment/types.d.ts +66 -0
- package/dist/sentiment/types.js +54 -0
- package/dist/sentiment/types.js.map +1 -0
- package/dist/system-prompt.js +9 -1
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.d.ts +3 -1
- package/dist/tools/fundamentals/company-overview.js +27 -27
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +45 -45
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +82 -82
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +3 -1
- package/dist/tools/fundamentals/earnings.js +25 -25
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.d.ts +3 -1
- package/dist/tools/fundamentals/financials.js +23 -23
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/index.js +8 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.js +28 -64
- package/dist/tools/interaction/ask-user.js.map +1 -1
- package/dist/tools/macro/fred-data.d.ts +3 -1
- package/dist/tools/macro/fred-data.js +26 -26
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +3 -1
- package/dist/tools/sentiment/reddit-sentiment.js +107 -22
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
- package/dist/tools/sentiment/sentiment-summary.js +230 -0
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
- package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
- package/dist/tools/sentiment/sentiment-trend.js +39 -0
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
- package/dist/tools/sentiment/twitter-sentiment.js +17 -0
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
- package/dist/tools/sentiment/web-search.d.ts +11 -0
- package/dist/tools/sentiment/web-search.js +115 -0
- package/dist/tools/sentiment/web-search.js.map +1 -0
- package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
- package/dist/tools/sentiment/web-sentiment.js +66 -0
- package/dist/tools/sentiment/web-sentiment.js.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/sentiment.d.ts +21 -0
- package/package.json +19 -3
- package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
- package/dist/tools/sentiment/news-sentiment.js +0 -55
- package/dist/tools/sentiment/news-sentiment.js.map +0 -1
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ProviderId } from "./providers.js";
|
|
2
|
+
export declare const ONBOARDING_VERSION = 2;
|
|
3
|
+
export type ProviderOnboardingEntry = {
|
|
4
|
+
status: "completed";
|
|
5
|
+
lastPromptAt: string;
|
|
6
|
+
} | {
|
|
7
|
+
status: "snoozed";
|
|
8
|
+
lastPromptAt: string;
|
|
9
|
+
snoozeUntil: string;
|
|
10
|
+
} | {
|
|
11
|
+
status: "never_ask";
|
|
12
|
+
lastPromptAt: string;
|
|
13
|
+
};
|
|
2
14
|
export interface OnboardingState {
|
|
3
15
|
version: number;
|
|
4
|
-
|
|
16
|
+
/** ISO 8601 timestamp the welcome message was first seeded, or undefined if never. */
|
|
17
|
+
welcomeShownAt?: string;
|
|
18
|
+
providers: Partial<Record<ProviderId, ProviderOnboardingEntry>>;
|
|
5
19
|
}
|
|
6
20
|
export declare function getDefaultOnboardingState(): OnboardingState;
|
|
7
21
|
export declare function loadOnboardingState(path?: string): OnboardingState;
|
|
8
22
|
export declare function saveOnboardingState(state: OnboardingState, path?: string): void;
|
|
23
|
+
export declare function markProviderCompleted(state: OnboardingState, id: ProviderId): OnboardingState;
|
|
24
|
+
export declare function markProviderSnoozed(state: OnboardingState, id: ProviderId, days: number): OnboardingState;
|
|
25
|
+
export declare function markProviderNeverAsk(state: OnboardingState, id: ProviderId): OnboardingState;
|
|
26
|
+
export declare function markWelcomeShown(state: OnboardingState): OnboardingState;
|
|
27
|
+
export declare function getProviderEntry(state: OnboardingState, id: ProviderId): ProviderOnboardingEntry | undefined;
|
|
28
|
+
export interface ShouldPromptOptions {
|
|
29
|
+
/**
|
|
30
|
+
* When true, treats a `completed` entry as eligible for re-prompt. This is
|
|
31
|
+
* how stale credentials (401 after a previously-connected key) get a fresh
|
|
32
|
+
* offer.
|
|
33
|
+
*/
|
|
34
|
+
stale?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function shouldPrompt(state: OnboardingState, id: ProviderId, now: Date, options?: ShouldPromptOptions): boolean;
|
|
37
|
+
export declare function shouldShowWelcome(state: OnboardingState, hasUI: boolean): boolean;
|
package/dist/onboarding/state.js
CHANGED
|
@@ -1,31 +1,159 @@
|
|
|
1
|
+
// Onboarding state schema and pure helpers.
|
|
2
|
+
//
|
|
3
|
+
// Shape: one flag for the welcome message (`welcomeShownAt`) plus a partial
|
|
4
|
+
// per-provider map with a proper discriminated union for status + snooze.
|
|
5
|
+
//
|
|
6
|
+
// All transition helpers are PURE — they return a new state object rather
|
|
7
|
+
// than mutating. The caller is responsible for persisting via saveOnboardingState.
|
|
1
8
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
9
|
import { ensureParentDir, getOnboardingPath } from "../infra/opencandle-paths.js";
|
|
3
|
-
export const ONBOARDING_VERSION =
|
|
10
|
+
export const ONBOARDING_VERSION = 2;
|
|
4
11
|
export function getDefaultOnboardingState() {
|
|
5
|
-
return { version: ONBOARDING_VERSION };
|
|
12
|
+
return { version: ONBOARDING_VERSION, providers: {} };
|
|
13
|
+
}
|
|
14
|
+
// -----------------------------------------------------------------------------
|
|
15
|
+
// Load / save
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
const STATUS_VALUES = new Set(["completed", "snoozed", "never_ask"]);
|
|
18
|
+
function parseEntry(raw) {
|
|
19
|
+
if (!raw || typeof raw !== "object")
|
|
20
|
+
return undefined;
|
|
21
|
+
const obj = raw;
|
|
22
|
+
const status = obj.status;
|
|
23
|
+
if (typeof status !== "string" || !STATUS_VALUES.has(status))
|
|
24
|
+
return undefined;
|
|
25
|
+
const lastPromptAt = obj.lastPromptAt;
|
|
26
|
+
if (typeof lastPromptAt !== "string")
|
|
27
|
+
return undefined;
|
|
28
|
+
if (status === "snoozed") {
|
|
29
|
+
const snoozeUntil = obj.snoozeUntil;
|
|
30
|
+
if (typeof snoozeUntil !== "string")
|
|
31
|
+
return undefined;
|
|
32
|
+
return { status: "snoozed", lastPromptAt, snoozeUntil };
|
|
33
|
+
}
|
|
34
|
+
if (status === "completed") {
|
|
35
|
+
return { status: "completed", lastPromptAt };
|
|
36
|
+
}
|
|
37
|
+
return { status: "never_ask", lastPromptAt };
|
|
38
|
+
}
|
|
39
|
+
function parseProvidersMap(raw) {
|
|
40
|
+
if (!raw || typeof raw !== "object")
|
|
41
|
+
return {};
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
44
|
+
const entry = parseEntry(value);
|
|
45
|
+
if (entry)
|
|
46
|
+
result[key] = entry;
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
6
49
|
}
|
|
7
50
|
export function loadOnboardingState(path = getOnboardingPath()) {
|
|
8
51
|
if (!existsSync(path)) {
|
|
9
52
|
return getDefaultOnboardingState();
|
|
10
53
|
}
|
|
54
|
+
let raw;
|
|
11
55
|
try {
|
|
12
|
-
|
|
13
|
-
if (!parsed || typeof parsed !== "object") {
|
|
14
|
-
return getDefaultOnboardingState();
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
version: typeof parsed.version === "number" ? parsed.version : ONBOARDING_VERSION,
|
|
18
|
-
financeSetupStatus: parsed.financeSetupStatus === "completed" || parsed.financeSetupStatus === "dismissed"
|
|
19
|
-
? parsed.financeSetupStatus
|
|
20
|
-
: undefined,
|
|
21
|
-
};
|
|
56
|
+
raw = readFileSync(path, "utf-8");
|
|
22
57
|
}
|
|
23
58
|
catch {
|
|
24
59
|
return getDefaultOnboardingState();
|
|
25
60
|
}
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(raw);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return getDefaultOnboardingState();
|
|
67
|
+
}
|
|
68
|
+
if (!parsed || typeof parsed !== "object") {
|
|
69
|
+
return getDefaultOnboardingState();
|
|
70
|
+
}
|
|
71
|
+
const obj = parsed;
|
|
72
|
+
const version = typeof obj.version === "number" ? obj.version : ONBOARDING_VERSION;
|
|
73
|
+
const welcomeShownAt = typeof obj.welcomeShownAt === "string" ? obj.welcomeShownAt : undefined;
|
|
74
|
+
const providers = parseProvidersMap(obj.providers);
|
|
75
|
+
return { version, welcomeShownAt, providers };
|
|
26
76
|
}
|
|
27
77
|
export function saveOnboardingState(state, path = getOnboardingPath()) {
|
|
28
78
|
ensureParentDir(path);
|
|
29
|
-
|
|
79
|
+
// Strip undefined fields for cleaner on-disk output.
|
|
80
|
+
const serializable = {
|
|
81
|
+
version: state.version,
|
|
82
|
+
providers: state.providers,
|
|
83
|
+
};
|
|
84
|
+
if (state.welcomeShownAt !== undefined) {
|
|
85
|
+
serializable.welcomeShownAt = state.welcomeShownAt;
|
|
86
|
+
}
|
|
87
|
+
writeFileSync(path, `${JSON.stringify(serializable, null, 2)}\n`, "utf-8");
|
|
88
|
+
}
|
|
89
|
+
// -----------------------------------------------------------------------------
|
|
90
|
+
// Pure transitions
|
|
91
|
+
// -----------------------------------------------------------------------------
|
|
92
|
+
function nowIso() {
|
|
93
|
+
return new Date().toISOString();
|
|
94
|
+
}
|
|
95
|
+
export function markProviderCompleted(state, id) {
|
|
96
|
+
return {
|
|
97
|
+
...state,
|
|
98
|
+
providers: {
|
|
99
|
+
...state.providers,
|
|
100
|
+
[id]: { status: "completed", lastPromptAt: nowIso() },
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export function markProviderSnoozed(state, id, days) {
|
|
105
|
+
const now = new Date();
|
|
106
|
+
const snoozeUntil = new Date(now.getTime() + days * 24 * 3600 * 1000);
|
|
107
|
+
return {
|
|
108
|
+
...state,
|
|
109
|
+
providers: {
|
|
110
|
+
...state.providers,
|
|
111
|
+
[id]: {
|
|
112
|
+
status: "snoozed",
|
|
113
|
+
lastPromptAt: now.toISOString(),
|
|
114
|
+
snoozeUntil: snoozeUntil.toISOString(),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function markProviderNeverAsk(state, id) {
|
|
120
|
+
return {
|
|
121
|
+
...state,
|
|
122
|
+
providers: {
|
|
123
|
+
...state.providers,
|
|
124
|
+
[id]: { status: "never_ask", lastPromptAt: nowIso() },
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export function markWelcomeShown(state) {
|
|
129
|
+
return { ...state, welcomeShownAt: nowIso() };
|
|
130
|
+
}
|
|
131
|
+
// -----------------------------------------------------------------------------
|
|
132
|
+
// Pure queries
|
|
133
|
+
// -----------------------------------------------------------------------------
|
|
134
|
+
export function getProviderEntry(state, id) {
|
|
135
|
+
return state.providers[id];
|
|
136
|
+
}
|
|
137
|
+
export function shouldPrompt(state, id, now, options = {}) {
|
|
138
|
+
const entry = state.providers[id];
|
|
139
|
+
if (!entry)
|
|
140
|
+
return true;
|
|
141
|
+
switch (entry.status) {
|
|
142
|
+
case "never_ask":
|
|
143
|
+
return false;
|
|
144
|
+
case "completed":
|
|
145
|
+
return options.stale === true;
|
|
146
|
+
case "snoozed": {
|
|
147
|
+
const until = Date.parse(entry.snoozeUntil);
|
|
148
|
+
if (Number.isNaN(until))
|
|
149
|
+
return true;
|
|
150
|
+
return now.getTime() >= until;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function shouldShowWelcome(state, hasUI) {
|
|
155
|
+
if (!hasUI)
|
|
156
|
+
return false;
|
|
157
|
+
return state.welcomeShownAt === undefined;
|
|
30
158
|
}
|
|
31
159
|
//# sourceMappingURL=state.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/onboarding/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/onboarding/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import { type ProviderId } from "./providers.js";
|
|
3
|
+
import { type CredentialRequiredReason } from "./tool-tags.js";
|
|
4
|
+
export interface CredentialRequiredDetails {
|
|
5
|
+
credentialRequired: {
|
|
6
|
+
provider: ProviderId;
|
|
7
|
+
reason: CredentialRequiredReason;
|
|
8
|
+
httpStatus?: number;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Wrap a tool's provider-calling logic with a credential-check layer.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```
|
|
16
|
+
* async execute(_, args) {
|
|
17
|
+
* return withCredentialCheck("alpha_vantage", async () => {
|
|
18
|
+
* const apiKey = getConfig().alphaVantageApiKey!;
|
|
19
|
+
* const result = await wrapProvider("alphavantage", () => getFinancials(args.symbol, apiKey));
|
|
20
|
+
* // ... format success result ...
|
|
21
|
+
* return { content: [...], details: [...] };
|
|
22
|
+
* });
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* On missing credential (upfront): `fn` is NOT called. A tagged tool result
|
|
27
|
+
* is returned directly.
|
|
28
|
+
*
|
|
29
|
+
* On `ProviderCredentialError` thrown during `fn`: the error is caught and a
|
|
30
|
+
* tagged tool result is returned.
|
|
31
|
+
*
|
|
32
|
+
* Any other thrown value (plain Error, string, etc.) is re-thrown unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function withCredentialCheck<T>(providerId: ProviderId, fn: () => Promise<AgentToolResult<T>>): Promise<AgentToolResult<T> | AgentToolResult<CredentialRequiredDetails>>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Tool-boundary helpers for credentialed providers.
|
|
2
|
+
//
|
|
3
|
+
// `withCredentialCheck` is the single-entry helper that OpenCandle tools use
|
|
4
|
+
// to wrap their provider calls. It centralizes:
|
|
5
|
+
// 1. The upfront "credential missing" check via the registry.
|
|
6
|
+
// 2. The catch-and-convert logic for `ProviderCredentialError` thrown from
|
|
7
|
+
// providers on 401/403 (stale credential after HTTP call).
|
|
8
|
+
// 3. The tagged `[OPENCANDLE_CREDENTIAL_REQUIRED ...]` content emission that
|
|
9
|
+
// the Pi `tool_result` extension hook intercepts.
|
|
10
|
+
//
|
|
11
|
+
// Non-credential errors (network timeouts, parse errors, etc.) are re-thrown
|
|
12
|
+
// unchanged so existing error handling in `wrapProvider` continues to work.
|
|
13
|
+
import { ProviderCredentialError } from "../providers/provider-credential-error.js";
|
|
14
|
+
import { getProvider, hasCredential } from "./providers.js";
|
|
15
|
+
import { buildCredentialRequiredTag, } from "./tool-tags.js";
|
|
16
|
+
function buildCredentialRequiredResult(provider, reason, httpStatus) {
|
|
17
|
+
const descriptor = getProvider(provider);
|
|
18
|
+
const tag = buildCredentialRequiredTag({
|
|
19
|
+
provider,
|
|
20
|
+
reason,
|
|
21
|
+
unlocks: descriptor.unlocks,
|
|
22
|
+
fallback: descriptor.fallbackDescription,
|
|
23
|
+
httpStatus,
|
|
24
|
+
});
|
|
25
|
+
// The second line is natural language describing what's missing. Users won't
|
|
26
|
+
// see it directly — the model sees it and uses the information to synthesize
|
|
27
|
+
// a gap note in its final answer. The tagged first line is the machine-
|
|
28
|
+
// readable signal the `tool_result` interception handler keys off of.
|
|
29
|
+
const narrative = reason === "missing"
|
|
30
|
+
? `${descriptor.displayName} was not fetched for this request because no API key is configured. It unlocks ${descriptor.unlocks.join(", ")}.`
|
|
31
|
+
: `${descriptor.displayName} rejected the configured API key (HTTP ${httpStatus ?? "auth error"}). It may be expired or revoked.`;
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: `${tag}\n\n${narrative}` }],
|
|
34
|
+
details: {
|
|
35
|
+
credentialRequired: {
|
|
36
|
+
provider,
|
|
37
|
+
reason,
|
|
38
|
+
...(httpStatus !== undefined ? { httpStatus } : {}),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Wrap a tool's provider-calling logic with a credential-check layer.
|
|
45
|
+
*
|
|
46
|
+
* Usage:
|
|
47
|
+
* ```
|
|
48
|
+
* async execute(_, args) {
|
|
49
|
+
* return withCredentialCheck("alpha_vantage", async () => {
|
|
50
|
+
* const apiKey = getConfig().alphaVantageApiKey!;
|
|
51
|
+
* const result = await wrapProvider("alphavantage", () => getFinancials(args.symbol, apiKey));
|
|
52
|
+
* // ... format success result ...
|
|
53
|
+
* return { content: [...], details: [...] };
|
|
54
|
+
* });
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* On missing credential (upfront): `fn` is NOT called. A tagged tool result
|
|
59
|
+
* is returned directly.
|
|
60
|
+
*
|
|
61
|
+
* On `ProviderCredentialError` thrown during `fn`: the error is caught and a
|
|
62
|
+
* tagged tool result is returned.
|
|
63
|
+
*
|
|
64
|
+
* Any other thrown value (plain Error, string, etc.) is re-thrown unchanged.
|
|
65
|
+
*/
|
|
66
|
+
export async function withCredentialCheck(providerId, fn) {
|
|
67
|
+
if (!hasCredential(providerId)) {
|
|
68
|
+
return buildCredentialRequiredResult(providerId, "missing");
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
return await fn();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error instanceof ProviderCredentialError) {
|
|
75
|
+
return buildCredentialRequiredResult(error.provider, error.reason, error.httpStatus);
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=tool-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-helpers.js","sourceRoot":"","sources":["../../src/onboarding/tool-helpers.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,6EAA6E;AAC7E,gDAAgD;AAChD,gEAAgE;AAChE,6EAA6E;AAC7E,gEAAgE;AAChE,+EAA+E;AAC/E,uDAAuD;AACvD,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAG5E,OAAO,EAAE,uBAAuB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,aAAa,EAAmB,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EACL,0BAA0B,GAE3B,MAAM,gBAAgB,CAAC;AAcxB,SAAS,6BAA6B,CACpC,QAAoB,EACpB,MAAgC,EAChC,UAAmB;IAEnB,MAAM,UAAU,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,0BAA0B,CAAC;QACrC,QAAQ;QACR,MAAM;QACN,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,QAAQ,EAAE,UAAU,CAAC,mBAAmB;QACxC,UAAU;KACX,CAAC,CAAC;IACH,6EAA6E;IAC7E,6EAA6E;IAC7E,wEAAwE;IACxE,sEAAsE;IACtE,MAAM,SAAS,GACb,MAAM,KAAK,SAAS;QAClB,CAAC,CAAC,GAAG,UAAU,CAAC,WAAW,kFAAkF,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAC7I,CAAC,CAAC,GAAG,UAAU,CAAC,WAAW,0CAA0C,UAAU,IAAI,YAAY,kCAAkC,CAAC;IAEtI,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,SAAS,EAAE,EAAE,CAAC;QAC3D,OAAO,EAAE;YACP,kBAAkB,EAAE;gBAClB,QAAQ;gBACR,MAAM;gBACN,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpD;SACF;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAsB,EACtB,EAAqC;IAErC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,6BAA6B,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,uBAAuB,EAAE,CAAC;YAC7C,OAAO,6BAA6B,CAClC,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,UAAU,CACjB,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ProviderId } from "./providers.js";
|
|
2
|
+
export type CredentialRequiredReason = "missing" | "stale";
|
|
3
|
+
export interface CredentialRequiredTagFields {
|
|
4
|
+
provider: ProviderId;
|
|
5
|
+
reason: CredentialRequiredReason;
|
|
6
|
+
unlocks: readonly string[];
|
|
7
|
+
fallback: string | null;
|
|
8
|
+
httpStatus?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SoftDegradedTagFields {
|
|
11
|
+
provider: ProviderId;
|
|
12
|
+
fallback: string;
|
|
13
|
+
remediation: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SkippedTagFields {
|
|
16
|
+
provider: ProviderId;
|
|
17
|
+
reason: "credential_not_provided";
|
|
18
|
+
remediation: string;
|
|
19
|
+
silenced?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ConnectedTagFields {
|
|
22
|
+
provider: ProviderId;
|
|
23
|
+
}
|
|
24
|
+
export type ParsedTag = ({
|
|
25
|
+
kind: "credential_required";
|
|
26
|
+
} & CredentialRequiredTagFields) | ({
|
|
27
|
+
kind: "soft_degraded";
|
|
28
|
+
} & SoftDegradedTagFields) | ({
|
|
29
|
+
kind: "skipped";
|
|
30
|
+
} & SkippedTagFields) | ({
|
|
31
|
+
kind: "connected";
|
|
32
|
+
} & ConnectedTagFields);
|
|
33
|
+
export declare function buildCredentialRequiredTag(fields: CredentialRequiredTagFields): string;
|
|
34
|
+
export declare function buildSoftDegradedTag(fields: SoftDegradedTagFields): string;
|
|
35
|
+
export declare function buildSkippedTag(fields: SkippedTagFields): string;
|
|
36
|
+
export declare function buildConnectedTag(fields: ConnectedTagFields): string;
|
|
37
|
+
export declare function parseToolTag(text: string): ParsedTag | undefined;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Tool-result text tags used by OpenCandle's conversational-provider-setup
|
|
2
|
+
// flow. These tagged lines are the LLM-facing contract — Pi provider adapters
|
|
3
|
+
// serialize the `content` field of tool results into the model's conversation,
|
|
4
|
+
// so any signal we want the model to reason over must live in text, not
|
|
5
|
+
// `details`.
|
|
6
|
+
//
|
|
7
|
+
// Tag format (single line, parseable):
|
|
8
|
+
// [OPENCANDLE_<KIND> field=value field2=value ... ]
|
|
9
|
+
//
|
|
10
|
+
// Values may be:
|
|
11
|
+
// - bare: provider=alpha_vantage reason=missing silenced=true httpStatus=401
|
|
12
|
+
// - quoted (for spaces or commas): remediation="run /connect economy"
|
|
13
|
+
// - lists (comma-separated inside quotes): unlocks="fundamentals, DCF"
|
|
14
|
+
// - null/absent: fallback=none
|
|
15
|
+
//
|
|
16
|
+
// Parser is tolerant of unknown fields and extra whitespace.
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
// Builders
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
function quote(value) {
|
|
21
|
+
return `"${value.replace(/"/g, '\\"')}"`;
|
|
22
|
+
}
|
|
23
|
+
function formatField(key, value) {
|
|
24
|
+
return `${key}=${value}`;
|
|
25
|
+
}
|
|
26
|
+
export function buildCredentialRequiredTag(fields) {
|
|
27
|
+
const parts = [
|
|
28
|
+
"[OPENCANDLE_CREDENTIAL_REQUIRED",
|
|
29
|
+
formatField("provider", fields.provider),
|
|
30
|
+
formatField("reason", fields.reason),
|
|
31
|
+
`unlocks=${quote(fields.unlocks.join(", "))}`,
|
|
32
|
+
formatField("fallback", fields.fallback ?? "none"),
|
|
33
|
+
];
|
|
34
|
+
if (fields.httpStatus !== undefined) {
|
|
35
|
+
parts.push(formatField("httpStatus", fields.httpStatus));
|
|
36
|
+
}
|
|
37
|
+
return `${parts.join(" ")}]`;
|
|
38
|
+
}
|
|
39
|
+
export function buildSoftDegradedTag(fields) {
|
|
40
|
+
return [
|
|
41
|
+
"[OPENCANDLE_SOFT_DEGRADED",
|
|
42
|
+
formatField("provider", fields.provider),
|
|
43
|
+
formatField("fallback", fields.fallback),
|
|
44
|
+
`remediation=${quote(fields.remediation)}`,
|
|
45
|
+
].join(" ") + "]";
|
|
46
|
+
}
|
|
47
|
+
export function buildSkippedTag(fields) {
|
|
48
|
+
const parts = [
|
|
49
|
+
"[OPENCANDLE_SKIPPED",
|
|
50
|
+
formatField("provider", fields.provider),
|
|
51
|
+
formatField("reason", fields.reason),
|
|
52
|
+
`remediation=${quote(fields.remediation)}`,
|
|
53
|
+
];
|
|
54
|
+
if (fields.silenced) {
|
|
55
|
+
parts.push(formatField("silenced", "true"));
|
|
56
|
+
}
|
|
57
|
+
return `${parts.join(" ")}]`;
|
|
58
|
+
}
|
|
59
|
+
export function buildConnectedTag(fields) {
|
|
60
|
+
return `[OPENCANDLE_CONNECTED provider=${fields.provider}]`;
|
|
61
|
+
}
|
|
62
|
+
// -----------------------------------------------------------------------------
|
|
63
|
+
// Parser
|
|
64
|
+
// -----------------------------------------------------------------------------
|
|
65
|
+
const TAG_LINE_REGEX = /\[OPENCANDLE_(CREDENTIAL_REQUIRED|SOFT_DEGRADED|SKIPPED|CONNECTED)([^\]]*)\]/;
|
|
66
|
+
function parseFields(raw) {
|
|
67
|
+
// Split on `key=value` pairs, respecting quoted values.
|
|
68
|
+
const fields = {};
|
|
69
|
+
const pattern = /(\w+)=("((?:[^"\\]|\\.)*)"|([^\s\]]+))/g;
|
|
70
|
+
let match;
|
|
71
|
+
while ((match = pattern.exec(raw)) !== null) {
|
|
72
|
+
const key = match[1];
|
|
73
|
+
const value = match[3] !== undefined ? match[3].replace(/\\"/g, '"') : match[4];
|
|
74
|
+
fields[key] = value;
|
|
75
|
+
}
|
|
76
|
+
return fields;
|
|
77
|
+
}
|
|
78
|
+
export function parseToolTag(text) {
|
|
79
|
+
const match = TAG_LINE_REGEX.exec(text);
|
|
80
|
+
if (!match)
|
|
81
|
+
return undefined;
|
|
82
|
+
const kind = match[1];
|
|
83
|
+
const rest = match[2];
|
|
84
|
+
const fields = parseFields(rest);
|
|
85
|
+
switch (kind) {
|
|
86
|
+
case "CREDENTIAL_REQUIRED": {
|
|
87
|
+
const provider = fields.provider;
|
|
88
|
+
const reason = fields.reason;
|
|
89
|
+
const unlocksRaw = fields.unlocks ?? "";
|
|
90
|
+
const fallbackRaw = fields.fallback;
|
|
91
|
+
if (!provider || (reason !== "missing" && reason !== "stale")) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const unlocks = unlocksRaw
|
|
95
|
+
.split(",")
|
|
96
|
+
.map((s) => s.trim())
|
|
97
|
+
.filter((s) => s.length > 0);
|
|
98
|
+
const fallback = fallbackRaw === undefined || fallbackRaw === "none" ? null : fallbackRaw;
|
|
99
|
+
const parsed = {
|
|
100
|
+
kind: "credential_required",
|
|
101
|
+
provider: provider,
|
|
102
|
+
reason: reason,
|
|
103
|
+
unlocks,
|
|
104
|
+
fallback,
|
|
105
|
+
};
|
|
106
|
+
if (fields.httpStatus !== undefined) {
|
|
107
|
+
const n = Number(fields.httpStatus);
|
|
108
|
+
if (!Number.isNaN(n))
|
|
109
|
+
parsed.httpStatus = n;
|
|
110
|
+
}
|
|
111
|
+
return parsed;
|
|
112
|
+
}
|
|
113
|
+
case "SOFT_DEGRADED": {
|
|
114
|
+
const { provider, fallback, remediation } = fields;
|
|
115
|
+
if (!provider || !fallback || !remediation)
|
|
116
|
+
return undefined;
|
|
117
|
+
return {
|
|
118
|
+
kind: "soft_degraded",
|
|
119
|
+
provider: provider,
|
|
120
|
+
fallback,
|
|
121
|
+
remediation,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
case "SKIPPED": {
|
|
125
|
+
const { provider, reason, remediation } = fields;
|
|
126
|
+
if (!provider || reason !== "credential_not_provided" || !remediation) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const parsed = {
|
|
130
|
+
kind: "skipped",
|
|
131
|
+
provider: provider,
|
|
132
|
+
reason: "credential_not_provided",
|
|
133
|
+
remediation,
|
|
134
|
+
};
|
|
135
|
+
if (fields.silenced === "true")
|
|
136
|
+
parsed.silenced = true;
|
|
137
|
+
return parsed;
|
|
138
|
+
}
|
|
139
|
+
case "CONNECTED": {
|
|
140
|
+
const { provider } = fields;
|
|
141
|
+
if (!provider)
|
|
142
|
+
return undefined;
|
|
143
|
+
return { kind: "connected", provider: provider };
|
|
144
|
+
}
|
|
145
|
+
default:
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=tool-tags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-tags.js","sourceRoot":"","sources":["../../src/onboarding/tool-tags.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProviderId } from "./providers.js";
|
|
2
|
+
export type ValidationResult = {
|
|
3
|
+
status: "valid";
|
|
4
|
+
} | {
|
|
5
|
+
status: "invalid";
|
|
6
|
+
httpStatus?: number;
|
|
7
|
+
message?: string;
|
|
8
|
+
} | {
|
|
9
|
+
status: "transient";
|
|
10
|
+
reason: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Validate a pasted credential against the provider's API. Returns a
|
|
14
|
+
* ValidationResult describing whether the key should be persisted.
|
|
15
|
+
*
|
|
16
|
+
* This function is side-effect-free beyond the network call: it does not
|
|
17
|
+
* touch the config file, onboarding state, or any in-memory cache.
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateCredential(providerId: ProviderId, key: string): Promise<ValidationResult>;
|