@v587d/capital-generation 2.8.3 → 2.9.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/CHANGELOG.md +81 -1
- package/README.md +40 -19
- package/chart-ui/README.md +5 -4
- package/chart-ui/client.src.cjs +7 -16
- package/chart-ui/index.js +12 -33
- package/chart-ui/package.json +2 -2
- package/config/client.js +18 -39
- package/config/client.src.cjs +79 -217
- package/config/index.js +22 -72
- package/config/package.json +2 -2
- package/cordis.patch.yml +46 -51
- package/lib/agents/bash-guard.d.ts +9 -55
- package/lib/agents/bash-guard.js +27 -67
- package/lib/agents/preset-roster.d.ts +22 -0
- package/lib/agents/preset-roster.js +87 -0
- package/lib/agents/role-tool-policy.d.ts +46 -0
- package/lib/agents/role-tool-policy.js +205 -0
- package/lib/agents/root-tool-policy.d.ts +28 -124
- package/lib/agents/root-tool-policy.js +164 -237
- package/lib/agents/selected-skills-provider.d.ts +2 -6
- package/lib/agents/selected-skills-provider.js +27 -84
- package/lib/capital-settings.d.ts +27 -0
- package/lib/capital-settings.js +23 -0
- package/lib/chart/artifact-ref.d.ts +13 -8
- package/lib/chart/artifact-ref.js +27 -10
- package/lib/chart/downsample.d.ts +2 -5
- package/lib/chart/downsample.js +3 -8
- package/lib/chart/errors.d.ts +3 -7
- package/lib/chart/errors.js +1 -2
- package/lib/chart/event-context.d.ts +8 -0
- package/lib/chart/event-context.js +39 -0
- package/lib/chart/events.d.ts +21 -78
- package/lib/chart/events.js +43 -189
- package/lib/chart/html.d.ts +2 -5
- package/lib/chart/html.js +8 -15
- package/lib/chart/present-tool.d.ts +28 -0
- package/lib/chart/present-tool.js +128 -0
- package/lib/chart/runtime.d.ts +5 -18
- package/lib/chart/runtime.js +14 -34
- package/lib/chart/series.d.ts +10 -17
- package/lib/chart/series.js +22 -47
- package/lib/chart/source-token.d.ts +5 -13
- package/lib/chart/source-token.js +18 -17
- package/lib/chart/spec.d.ts +6 -12
- package/lib/chart/spec.js +7 -31
- package/lib/chart/tool.d.ts +7 -29
- package/lib/chart/tool.js +15 -25
- package/lib/chart-render-entry.d.ts +10 -0
- package/lib/chart-render-entry.js +14 -0
- package/lib/chart-source-entry.d.ts +13 -0
- package/lib/chart-source-entry.js +20 -0
- package/lib/collect-entry.d.ts +9 -0
- package/lib/collect-entry.js +10 -0
- package/lib/collector-runtime.d.ts +22 -0
- package/lib/collector-runtime.js +97 -0
- package/lib/data-collector/args.d.ts +6 -24
- package/lib/data-collector/args.js +6 -24
- package/lib/data-collector/dataset-tools.d.ts +7 -1
- package/lib/data-collector/dataset-tools.js +27 -56
- package/lib/data-collector/describe.d.ts +8 -33
- package/lib/data-collector/describe.js +17 -70
- package/lib/data-collector/hub.d.ts +27 -85
- package/lib/data-collector/hub.js +19 -85
- package/lib/data-collector/query-time.d.ts +7 -26
- package/lib/data-collector/query-time.js +8 -27
- package/lib/data-collector/store.d.ts +41 -117
- package/lib/data-collector/store.js +32 -111
- package/lib/data-collector/time-axis.d.ts +16 -76
- package/lib/data-collector/time-axis.js +25 -111
- package/lib/data-collector/tools.d.ts +6 -6
- package/lib/data-collector/tools.js +34 -43
- package/lib/dataset-read-entry.d.ts +9 -0
- package/lib/dataset-read-entry.js +7 -0
- package/lib/index.d.ts +7 -117
- package/lib/index.js +43 -422
- package/lib/net/eastmoney-client.d.ts +13 -65
- package/lib/net/eastmoney-client.js +12 -53
- package/lib/net/throttle.d.ts +2 -13
- package/lib/net/throttle.js +3 -14
- package/lib/ocr/client.d.ts +12 -21
- package/lib/ocr/client.js +23 -40
- package/lib/ocr/markdown.d.ts +20 -26
- package/lib/ocr/markdown.js +23 -36
- package/lib/ocr/store.d.ts +14 -47
- package/lib/ocr/store.js +15 -45
- package/lib/ocr-entry.d.ts +9 -0
- package/lib/ocr-entry.js +10 -0
- package/lib/policy-entry.d.ts +17 -0
- package/lib/policy-entry.js +22 -0
- package/lib/present-entry.d.ts +9 -0
- package/lib/present-entry.js +13 -0
- package/lib/preset-claim.d.ts +22 -0
- package/lib/preset-claim.js +24 -0
- package/lib/preset-ids.d.ts +38 -0
- package/lib/preset-ids.js +46 -0
- package/lib/retriever-config.d.ts +37 -0
- package/lib/retriever-config.js +36 -0
- package/lib/retriever-runtime.d.ts +31 -0
- package/lib/retriever-runtime.js +82 -0
- package/lib/root-extras-entry.d.ts +17 -0
- package/lib/root-extras-entry.js +25 -0
- package/lib/runtime-entry.d.ts +15 -0
- package/lib/runtime-entry.js +5 -0
- package/lib/runtime.d.ts +21 -0
- package/lib/runtime.js +33 -0
- package/lib/search-entry.d.ts +9 -0
- package/lib/search-entry.js +9 -0
- package/lib/sources/eastmoney-http.js +20 -68
- package/lib/sources/ftshare-rest.d.ts +6 -34
- package/lib/sources/ftshare-rest.js +84 -292
- package/lib/sources/fuyao-core.d.ts +8 -18
- package/lib/sources/fuyao-core.js +10 -21
- package/lib/sources/fuyao-rest.d.ts +2 -6
- package/lib/sources/fuyao-rest.js +23 -102
- package/lib/sources/security-code.d.ts +6 -12
- package/lib/sources/security-code.js +6 -12
- package/lib/sources/security-identity.d.ts +1 -4
- package/lib/sources/security-identity.js +3 -8
- package/lib/sources/tencent-http.js +14 -28
- package/lib/sources/tencent-public-core.d.ts +8 -60
- package/lib/sources/tencent-public-core.js +12 -77
- package/lib/sources/wind-mcp.d.ts +0 -5
- package/lib/sources/wind-mcp.js +23 -78
- package/lib/sources-entry.d.ts +9 -0
- package/lib/sources-entry.js +9 -0
- package/lib/time/tools.d.ts +8 -31
- package/lib/time/tools.js +30 -66
- package/lib/time-entry.d.ts +9 -0
- package/lib/time-entry.js +7 -0
- package/lib/tool-exec.d.ts +45 -11
- package/lib/tool-exec.js +59 -6
- package/lib/user-customization.d.ts +14 -0
- package/lib/user-customization.js +47 -0
- package/lib/watchlist/tools.d.ts +3 -13
- package/lib/watchlist/tools.js +10 -30
- package/lib/watchlist-entry.d.ts +9 -0
- package/lib/watchlist-entry.js +6 -0
- package/lib/web-retriever/engines.d.ts +6 -11
- package/lib/web-retriever/engines.js +9 -14
- package/lib/web-retriever/html-markdown.d.ts +14 -34
- package/lib/web-retriever/html-markdown.js +39 -75
- package/lib/web-retriever/local-fetch.d.ts +22 -58
- package/lib/web-retriever/local-fetch.js +33 -77
- package/lib/web-retriever/retriever.d.ts +6 -18
- package/lib/web-retriever/retriever.js +7 -7
- package/lib/web-retriever/sources.d.ts +37 -124
- package/lib/web-retriever/sources.js +64 -196
- package/lib/web-retriever/tools.d.ts +43 -27
- package/lib/web-retriever/tools.js +99 -137
- package/lib/web-retriever/wind-client.d.ts +14 -31
- package/lib/web-retriever/wind-client.js +16 -32
- package/locale/zh.json +1 -1
- package/package.json +72 -7
- package/preset/capital-generation/README.md +87 -53
- package/preset/capital-generation/agent.patch.yml +532 -337
- package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +6 -4
- package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
- package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -2
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +30 -33
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +14 -16
- package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
- package/watchlist/client.js +15 -148
- package/watchlist/client.src.cjs +148 -662
- package/watchlist/holding-rules.js +11 -38
- package/watchlist/index.js +127 -397
- package/watchlist/package.json +2 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface WindDocsConfig {
|
|
2
|
+
endpoint?: string;
|
|
3
|
+
credentialRef?: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface LocalFetchConfig {
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
maxBytes?: number;
|
|
10
|
+
maxContentChars?: number;
|
|
11
|
+
maxRedirects?: number;
|
|
12
|
+
userAgent?: string;
|
|
13
|
+
}
|
|
14
|
+
/** `pollBudgetMs` must stay below the tool's declared 240000 ms timeout: the margin is for
|
|
15
|
+
* persisting the pending receipt, without which a framework timeout leaves no `doc_id`. */
|
|
16
|
+
export interface PaddleOcrConfig {
|
|
17
|
+
endpoint?: string;
|
|
18
|
+
credentialRef?: string;
|
|
19
|
+
model?: string;
|
|
20
|
+
pollBudgetMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface RetrieverConfig {
|
|
23
|
+
baseURL?: string;
|
|
24
|
+
credentialRef?: string;
|
|
25
|
+
windDocs?: WindDocsConfig;
|
|
26
|
+
localFetch?: LocalFetchConfig;
|
|
27
|
+
paddleOcr?: PaddleOcrConfig;
|
|
28
|
+
}
|
|
29
|
+
/** Sole default source for both the schema and `resolveLocalFetchConfig()`.
|
|
30
|
+
* timeoutMs 30000 matches dsh-web-fetch-http; 15s misreads slow proxy failover as TIMEOUT.
|
|
31
|
+
* maxContentChars applies to raw HTML before markdown conversion, so size it for HTML.
|
|
32
|
+
* userAgent is a product token: a bare version looks like a crawler to WAFs. */
|
|
33
|
+
export declare const LOCAL_FETCH_DEFAULTS: Required<LocalFetchConfig>;
|
|
34
|
+
/** Empty endpoint/model defer to `src/ocr/client.ts`; pollBudgetMs 0 defers to its DEFAULT_WAIT_BUDGET_MS. */
|
|
35
|
+
export declare const PADDLE_OCR_DEFAULTS: Required<PaddleOcrConfig>;
|
|
36
|
+
/** Consumers fill defaults themselves: `apply()` can receive a raw object that never passed the schema. */
|
|
37
|
+
export declare function resolveLocalFetchConfig(config?: LocalFetchConfig): Required<LocalFetchConfig>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** web_retriever configuration surface: types, defaults, and consumer-side default filling.
|
|
2
|
+
* Kept free of Cordis imports so role entries can reuse it without cycling through `index.js`. */
|
|
3
|
+
import { LOCAL_FETCH_CLIENT_VERSION } from './web-retriever/local-fetch.js';
|
|
4
|
+
/** Sole default source for both the schema and `resolveLocalFetchConfig()`.
|
|
5
|
+
* timeoutMs 30000 matches dsh-web-fetch-http; 15s misreads slow proxy failover as TIMEOUT.
|
|
6
|
+
* maxContentChars applies to raw HTML before markdown conversion, so size it for HTML.
|
|
7
|
+
* userAgent is a product token: a bare version looks like a crawler to WAFs. */
|
|
8
|
+
export const LOCAL_FETCH_DEFAULTS = {
|
|
9
|
+
enabled: true,
|
|
10
|
+
timeoutMs: 30_000,
|
|
11
|
+
maxBytes: 524_288,
|
|
12
|
+
maxContentChars: 100_000,
|
|
13
|
+
maxRedirects: 5,
|
|
14
|
+
userAgent: '@v587d/capital-generation',
|
|
15
|
+
};
|
|
16
|
+
/** Empty endpoint/model defer to `src/ocr/client.ts`; pollBudgetMs 0 defers to its DEFAULT_WAIT_BUDGET_MS. */
|
|
17
|
+
export const PADDLE_OCR_DEFAULTS = {
|
|
18
|
+
endpoint: '',
|
|
19
|
+
credentialRef: 'PADDLE_OCR_TOKEN',
|
|
20
|
+
model: '',
|
|
21
|
+
pollBudgetMs: 0,
|
|
22
|
+
};
|
|
23
|
+
/** Consumers fill defaults themselves: `apply()` can receive a raw object that never passed the schema. */
|
|
24
|
+
export function resolveLocalFetchConfig(config) {
|
|
25
|
+
const source = config ?? {};
|
|
26
|
+
// UA tri-state: unset → product default; empty string → plugin version; explicit value → as-is.
|
|
27
|
+
const userAgent = source.userAgent ?? LOCAL_FETCH_DEFAULTS.userAgent;
|
|
28
|
+
return {
|
|
29
|
+
enabled: source.enabled !== false,
|
|
30
|
+
timeoutMs: source.timeoutMs ?? LOCAL_FETCH_DEFAULTS.timeoutMs,
|
|
31
|
+
maxBytes: source.maxBytes ?? LOCAL_FETCH_DEFAULTS.maxBytes,
|
|
32
|
+
maxContentChars: source.maxContentChars ?? LOCAL_FETCH_DEFAULTS.maxContentChars,
|
|
33
|
+
maxRedirects: source.maxRedirects ?? LOCAL_FETCH_DEFAULTS.maxRedirects,
|
|
34
|
+
userAgent: userAgent || LOCAL_FETCH_CLIENT_VERSION,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type WindClient } from './web-retriever/wind-client.js';
|
|
2
|
+
import { type PaddleOcrClient } from './ocr/client.js';
|
|
3
|
+
import { WebRetriever, type LocalFetcherLike } from './web-retriever/retriever.js';
|
|
4
|
+
import { type LocalFetchConfig, type RetrieverConfig } from './retriever-config.js';
|
|
5
|
+
interface CredentialContext {
|
|
6
|
+
get(name: string): unknown;
|
|
7
|
+
}
|
|
8
|
+
/** credentials first, env var fallback; resolve per call, never cache (boot-time races). */
|
|
9
|
+
export declare function resolveRetrieverCredential(ctx: CredentialContext, ref: string): Promise<string | undefined>;
|
|
10
|
+
/** Wind ref + per-call resolver; `wind_docs` and `wind_*` market data share the key. */
|
|
11
|
+
export declare function createWindCredentialResolver(ctx: CredentialContext, credentialRef?: string): {
|
|
12
|
+
credentialRef: string;
|
|
13
|
+
resolve: () => Promise<string | undefined>;
|
|
14
|
+
};
|
|
15
|
+
export interface RetrieverRuntime {
|
|
16
|
+
retriever: WebRetriever;
|
|
17
|
+
windClient: WindClient;
|
|
18
|
+
ocrClient: PaddleOcrClient;
|
|
19
|
+
localFetchConfig: Required<LocalFetchConfig>;
|
|
20
|
+
resolveWindApiKey: () => Promise<string | undefined>;
|
|
21
|
+
/** Reference name only (for logs/diagnostics), never the secret. */
|
|
22
|
+
windCredentialRef: string;
|
|
23
|
+
/** Present only when localFetch.enabled is true. */
|
|
24
|
+
localFetch?: LocalFetcherLike;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `config` is the raw, unvalidated object from preset/settings, so every default must be
|
|
28
|
+
* applied here rather than relying on Schemastery.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createRetrieverRuntime(ctx: CredentialContext, config?: RetrieverConfig): RetrieverRuntime;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sole construction point for AnySearch / Wind / local fetch / PaddleOCR / WebRetriever.
|
|
3
|
+
* Search, source and OCR rows reuse it; copies would drift defaults, credential
|
|
4
|
+
* resolution and OCR budgets. Registers no tools or services.
|
|
5
|
+
*/
|
|
6
|
+
import { createAnySearchClient, envKey } from './web-retriever/engines.js';
|
|
7
|
+
import { createHttpRequester, createLocalFetcher } from './web-retriever/local-fetch.js';
|
|
8
|
+
import { createWindClient } from './web-retriever/wind-client.js';
|
|
9
|
+
import { createPaddleOcrClient } from './ocr/client.js';
|
|
10
|
+
import { WebRetriever } from './web-retriever/retriever.js';
|
|
11
|
+
import { PADDLE_OCR_DEFAULTS, resolveLocalFetchConfig, } from './retriever-config.js';
|
|
12
|
+
/** credentials first, env var fallback; resolve per call, never cache (boot-time races). */
|
|
13
|
+
export async function resolveRetrieverCredential(ctx, ref) {
|
|
14
|
+
try {
|
|
15
|
+
const credentials = ctx.get('credentials');
|
|
16
|
+
const resolved = await credentials?.resolve?.(ref);
|
|
17
|
+
if (resolved?.value)
|
|
18
|
+
return resolved.value;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Fall back to the env var when the credentials service is unavailable.
|
|
22
|
+
}
|
|
23
|
+
return envKey(ref);
|
|
24
|
+
}
|
|
25
|
+
/** Wind ref + per-call resolver; `wind_docs` and `wind_*` market data share the key. */
|
|
26
|
+
export function createWindCredentialResolver(ctx, credentialRef = 'WIND_API_KEY') {
|
|
27
|
+
const ref = credentialRef || 'WIND_API_KEY';
|
|
28
|
+
return { credentialRef: ref, resolve: () => resolveRetrieverCredential(ctx, ref) };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `config` is the raw, unvalidated object from preset/settings, so every default must be
|
|
32
|
+
* applied here rather than relying on Schemastery.
|
|
33
|
+
*/
|
|
34
|
+
export function createRetrieverRuntime(ctx, config = {}) {
|
|
35
|
+
const credentialRef = config.credentialRef || 'ANYSEARCH_API_KEY';
|
|
36
|
+
const client = createAnySearchClient(config.baseURL || undefined, () => resolveRetrieverCredential(ctx, credentialRef));
|
|
37
|
+
const windDocs = config.windDocs ?? {};
|
|
38
|
+
const windCredential = createWindCredentialResolver(ctx, windDocs.credentialRef);
|
|
39
|
+
const windCredentialRef = windCredential.credentialRef;
|
|
40
|
+
const resolveWindApiKey = windCredential.resolve;
|
|
41
|
+
const windClient = createWindClient({
|
|
42
|
+
endpoint: windDocs.endpoint || undefined,
|
|
43
|
+
timeoutMs: windDocs.timeoutMs || undefined,
|
|
44
|
+
resolveApiKey: resolveWindApiKey,
|
|
45
|
+
});
|
|
46
|
+
const localFetchConfig = resolveLocalFetchConfig(config.localFetch);
|
|
47
|
+
const localFetch = localFetchConfig.enabled
|
|
48
|
+
? createLocalFetcher({
|
|
49
|
+
timeoutMs: localFetchConfig.timeoutMs,
|
|
50
|
+
maxBytes: localFetchConfig.maxBytes,
|
|
51
|
+
maxContentChars: localFetchConfig.maxContentChars,
|
|
52
|
+
maxRedirects: localFetchConfig.maxRedirects,
|
|
53
|
+
userAgent: localFetchConfig.userAgent,
|
|
54
|
+
})
|
|
55
|
+
: undefined;
|
|
56
|
+
const paddleOcrConfig = config.paddleOcr ?? {};
|
|
57
|
+
const paddleOcrCredentialRef = paddleOcrConfig.credentialRef || PADDLE_OCR_DEFAULTS.credentialRef;
|
|
58
|
+
const ocrClient = createPaddleOcrClient({
|
|
59
|
+
endpoint: paddleOcrConfig.endpoint || undefined,
|
|
60
|
+
model: paddleOcrConfig.model || undefined,
|
|
61
|
+
...(paddleOcrConfig.pollBudgetMs ? { waitBudgetMs: paddleOcrConfig.pollBudgetMs } : {}),
|
|
62
|
+
resolveToken: () => resolveRetrieverCredential(ctx, paddleOcrCredentialRef),
|
|
63
|
+
// Reuse the local-fetch requester (URL/IP gates, size and timeout caps). 120s because
|
|
64
|
+
// PDF multipart upload and whole-document JSONL exceed the 30s web-page budget.
|
|
65
|
+
request: createHttpRequester({
|
|
66
|
+
timeoutMs: 120_000,
|
|
67
|
+
maxBytes: 1_048_576,
|
|
68
|
+
maxContentChars: 1_048_576,
|
|
69
|
+
maxRedirects: localFetchConfig.maxRedirects,
|
|
70
|
+
userAgent: localFetchConfig.userAgent,
|
|
71
|
+
}).request,
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
retriever: new WebRetriever(client, localFetch),
|
|
75
|
+
windClient,
|
|
76
|
+
ocrClient,
|
|
77
|
+
localFetchConfig,
|
|
78
|
+
resolveWindApiKey,
|
|
79
|
+
windCredentialRef,
|
|
80
|
+
...(localFetch === undefined ? {} : { localFetch }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tools/root-extras` row: root-only selected-skills provider and the `customPersona`
|
|
3
|
+
* system-prompt section. This row registers no tools; both pieces re-read settings
|
|
4
|
+
* every turn so saved changes apply on the next step.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import { type SelectedSkillSelection } from './agents/selected-skills-provider.js';
|
|
8
|
+
export declare const name = "capital-root-extras";
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
interface RootExtrasConfig {
|
|
11
|
+
customPersona?: string;
|
|
12
|
+
selectedSkills?: SelectedSkillSelection;
|
|
13
|
+
}
|
|
14
|
+
/** Re-read per turn so a settings save applies to the same session on the next step. */
|
|
15
|
+
export declare function readRootExtrasConfig(ctx: Context): RootExtrasConfig;
|
|
16
|
+
export declare function apply(ctx: Context): void;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { registerRootSelectedSkills } from './agents/selected-skills-provider.js';
|
|
2
|
+
import { userCustomizationSection } from './user-customization.js';
|
|
3
|
+
import { readCapitalConfig } from './capital-settings.js';
|
|
4
|
+
export const name = 'capital-root-extras';
|
|
5
|
+
export const inject = ['systemPrompt'];
|
|
6
|
+
/** Re-read per turn so a settings save applies to the same session on the next step. */
|
|
7
|
+
export function readRootExtrasConfig(ctx) {
|
|
8
|
+
const config = readCapitalConfig(ctx);
|
|
9
|
+
return { customPersona: config.customPersona, selectedSkills: config.selectedSkills };
|
|
10
|
+
}
|
|
11
|
+
export function apply(ctx) {
|
|
12
|
+
registerRootSelectedSkills(ctx, () => readRootExtrasConfig(ctx).selectedSkills ?? {});
|
|
13
|
+
let lastPersonaError;
|
|
14
|
+
const section = userCustomizationSection(() => readRootExtrasConfig(ctx).customPersona, (error) => {
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
if (message === lastPersonaError)
|
|
17
|
+
return;
|
|
18
|
+
lastPersonaError = message;
|
|
19
|
+
ctx.logger?.warn?.(`capital-generation: customPersona 未加载(${message})`);
|
|
20
|
+
});
|
|
21
|
+
ctx.effect(() => {
|
|
22
|
+
const dispose = ctx.systemPrompt.section(section);
|
|
23
|
+
return typeof dispose === 'function' ? dispose : () => { };
|
|
24
|
+
}, 'capital-generation.user-customization()');
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-plane Capital runtime row. Chart source tokens are issued by data_junior and
|
|
3
|
+
* redeemed by the specialist, so both presets must see one registry; provide it here
|
|
4
|
+
* instead of per preset. The module-level cache still keys by root for older call sites.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import { type CapitalShared } from './runtime.js';
|
|
8
|
+
declare module '@deepseek-ai/cordis' {
|
|
9
|
+
interface Context {
|
|
10
|
+
/** Shared by the Capital root, junior and specialist presets. */
|
|
11
|
+
capitalChartRuntime: CapitalShared;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export declare const name = "capital-runtime";
|
|
15
|
+
export declare function apply(ctx: Context): void;
|
package/lib/runtime.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { WorkspaceDatasetStore } from './data-collector/store.js';
|
|
2
|
+
import { ChartArtifactRegistry } from './chart/artifact-ref.js';
|
|
3
|
+
import { ChartSourceTokenStore } from './chart/source-token.js';
|
|
4
|
+
/** Structural type so tests can pass a fake ctx. */
|
|
5
|
+
export interface RuntimeContext {
|
|
6
|
+
get(name: string): unknown;
|
|
7
|
+
root?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface CapitalShared {
|
|
10
|
+
sourceTokens: ChartSourceTokenStore;
|
|
11
|
+
artifacts: ChartArtifactRegistry;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One Dataset store per mount, deliberately not a singleton: the hub registers source
|
|
15
|
+
* capabilities once per isolate realm, so a shared hub throws `data source capability
|
|
16
|
+
* already registered`. Stores only touch workspace via host fs, no state to synchronize.
|
|
17
|
+
*/
|
|
18
|
+
export declare function capitalDatasetStore(ctx: RuntimeContext): WorkspaceDatasetStore;
|
|
19
|
+
export declare function capitalSharedRuntime(ctx: RuntimeContext): CapitalShared;
|
|
20
|
+
/** Test-only: drop the per-root cache. */
|
|
21
|
+
export declare function resetCapitalRuntime(): void;
|
package/lib/runtime.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { WorkspaceDatasetStore } from './data-collector/store.js';
|
|
2
|
+
import { ChartArtifactRegistry } from './chart/artifact-ref.js';
|
|
3
|
+
import { ChartSourceTokenStore } from './chart/source-token.js';
|
|
4
|
+
/**
|
|
5
|
+
* One Dataset store per mount, deliberately not a singleton: the hub registers source
|
|
6
|
+
* capabilities once per isolate realm, so a shared hub throws `data source capability
|
|
7
|
+
* already registered`. Stores only touch workspace via host fs, no state to synchronize.
|
|
8
|
+
*/
|
|
9
|
+
export function capitalDatasetStore(ctx) {
|
|
10
|
+
return new WorkspaceDatasetStore({
|
|
11
|
+
fs: ctx.get('fs'),
|
|
12
|
+
sandboxPolicy: ctx.get('sandboxPolicy'),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Process-wide state shared by every mounted preset; `apply()` runs once per role preset.
|
|
17
|
+
* Per-role token stores would make `render_chart` silently report `chart_source_expired`.
|
|
18
|
+
* Key by `ctx.root` (stable) because mount points differ per application.
|
|
19
|
+
*/
|
|
20
|
+
const cached = new Map();
|
|
21
|
+
export function capitalSharedRuntime(ctx) {
|
|
22
|
+
const ref = ctx.root ?? ctx;
|
|
23
|
+
const existing = cached.get(ref);
|
|
24
|
+
if (existing !== undefined)
|
|
25
|
+
return existing;
|
|
26
|
+
const shared = { sourceTokens: new ChartSourceTokenStore(), artifacts: new ChartArtifactRegistry() };
|
|
27
|
+
cached.set(ref, shared);
|
|
28
|
+
return shared;
|
|
29
|
+
}
|
|
30
|
+
/** Test-only: drop the per-root cache. */
|
|
31
|
+
export function resetCapitalRuntime() {
|
|
32
|
+
cached.clear();
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tools/retrieve-search` row: anysearch_search / web_retriever_fetch / wind_docs_*.
|
|
3
|
+
* Split from source and OCR rows; all share `retriever-runtime.ts` and resolve
|
|
4
|
+
* credentials per call.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
export declare const name = "capital-retrieve-search-tools";
|
|
8
|
+
export declare const inject: string[];
|
|
9
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createRetrieverRuntime } from './retriever-runtime.js';
|
|
2
|
+
import { registerSearchTools } from './web-retriever/tools.js';
|
|
3
|
+
import { readRetrieverConfig } from './capital-settings.js';
|
|
4
|
+
export const name = 'capital-retrieve-search-tools';
|
|
5
|
+
export const inject = ['tools'];
|
|
6
|
+
export function apply(ctx) {
|
|
7
|
+
const runtime = createRetrieverRuntime(ctx, readRetrieverConfig(ctx));
|
|
8
|
+
registerSearchTools(ctx, runtime.retriever, runtime.windClient);
|
|
9
|
+
}
|
|
@@ -99,16 +99,8 @@ function dateMs(value) { return Date.parse(`${value}T00:00:00+08:00`); }
|
|
|
99
99
|
function textOrNull(value) { return value === undefined || value === null || value === '' ? null : String(value); }
|
|
100
100
|
function sourceError(message, code = 'eastmoney_source_error') { const error = new Error(message); error.code = code; return error; }
|
|
101
101
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* 为什么必须共享:东财按**出口 IP** 风控(社区实测 >5 次/秒、1 分钟 ≥200 次、5 分钟 ≥300 次
|
|
105
|
-
* 即临时封禁),而本仓曾经有**两份互不知情**的东财出口——数据面这里的裸 `fetch`,与
|
|
106
|
-
* web_retriever 具名来源工具自己的一份。两份独立限流等于没有限流。
|
|
107
|
-
* 现在两条数据面共用同一个节流器:数据面按"可复用性"分层(数值/分页走 Hub、流式文本走
|
|
108
|
-
* web_retriever),但**网络面只有一份**。
|
|
109
|
-
*
|
|
110
|
-
* 本函数原有的错误码语义(`eastmoney_rate_limit` / `eastmoney_http_error` /
|
|
111
|
-
* `eastmoney_invalid_response`)刻意保持不变——收敛的是网络面,不是调用方的契约。
|
|
102
|
+
* All Eastmoney calls use the process-wide client. Limits are per egress IP (>5/s, >=200/min,
|
|
103
|
+
* >=300/5min bans); two throttles mean no throttle. Error codes stay unchanged.
|
|
112
104
|
*/
|
|
113
105
|
async function getJson(url, signal, context) {
|
|
114
106
|
let payload;
|
|
@@ -116,7 +108,6 @@ async function getJson(url, signal, context) {
|
|
|
116
108
|
payload = await sharedEastmoneyClient().fetchJson(url, { signal });
|
|
117
109
|
}
|
|
118
110
|
catch (error) {
|
|
119
|
-
// 调用方取消原样抛出;上游 HTTP 错误状态按既有口径归类。
|
|
120
111
|
if (error instanceof Error && error.name === 'AbortError')
|
|
121
112
|
throw error;
|
|
122
113
|
const status = error instanceof EastmoneyTransportError ? error.status : undefined;
|
|
@@ -129,9 +120,7 @@ async function getJson(url, signal, context) {
|
|
|
129
120
|
return payload;
|
|
130
121
|
}
|
|
131
122
|
function requireDatacenterResult(payload, context) {
|
|
132
|
-
//
|
|
133
|
-
// 节假日、或该窗口确实没有任何记录。归类成 eastmoney_upstream_error 会诱导模型反复重试
|
|
134
|
-
// (§10.4 明令"数据缺口不要重试"),也与单票路径把 9201 判成"无记录"自相矛盾。
|
|
123
|
+
// code 9201 = empty result (holiday/no records), a real data gap; treating it as a failure invites retries.
|
|
135
124
|
if (payload.success !== true && Number(payload.code) === 9201)
|
|
136
125
|
return { data: [], pages: 0, count: 0 };
|
|
137
126
|
if (payload.success !== true || Number(payload.code) !== 0 || !isRecord(payload.result))
|
|
@@ -201,9 +190,8 @@ function topFilter(params, ticker) {
|
|
|
201
190
|
return code ? `${base}(SECURITY_CODE="${code}")` : base;
|
|
202
191
|
}
|
|
203
192
|
function parseTopRow(raw) {
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
// 整页龙虎榜判成错误(护栏误杀真实数据,§10.2)。行里的 `market` 字段仍原样保留。
|
|
193
|
+
// MARKET is free text and redundant when SECUCODE has a suffix; only require it for bare codes,
|
|
194
|
+
// otherwise a whitelist rejects valid billboard pages. Keep the raw `market` field.
|
|
207
195
|
const secucode = typeof raw.SECUCODE === 'string' ? raw.SECUCODE : '';
|
|
208
196
|
const identity = secucode.includes('.')
|
|
209
197
|
? normalizeEastmoneySecurityIdentity({ secucode })
|
|
@@ -472,12 +460,7 @@ const MACRO_TABLES = [
|
|
|
472
460
|
texts: [{ from: 'REMARK', to: 'announcement' }],
|
|
473
461
|
},
|
|
474
462
|
];
|
|
475
|
-
/**
|
|
476
|
-
* 沪深港通六个渠道(`docs/dev/tool-schema.md` §10.6:上游给的是 `001`…`006` 这种数字码,
|
|
477
|
-
* 模型侧只认语义名)。渠道身份由上游自己标注的 `MUTUAL_TYPE_NAME` 核对,并用同日的
|
|
478
|
-
* `DEAL_AMT` 加法复核过:`001+003=005`(北向合计)、`002+004=006`(南向合计),实测 2026-09-30
|
|
479
|
-
* 两条等式都精确成立。
|
|
480
|
-
*/
|
|
463
|
+
/** Stock Connect channels: upstream codes 001–006; 001+003=005 (north) and 002+004=006 (south) verified. */
|
|
481
464
|
const MUTUAL_CHANNELS = {
|
|
482
465
|
sh_stock_connect: '001',
|
|
483
466
|
hk_connect_sh: '002',
|
|
@@ -567,7 +550,7 @@ const mutualQuotaRow = {
|
|
|
567
550
|
},
|
|
568
551
|
additionalProperties: true,
|
|
569
552
|
};
|
|
570
|
-
/**
|
|
553
|
+
/** Quota table has single channels only, no totals. */
|
|
571
554
|
const MUTUAL_QUOTA_CHANNELS = { 沪股通: 'sh_stock_connect', 深股通: 'sz_stock_connect', '港股通(沪)': 'hk_connect_sh', '港股通(深)': 'hk_connect_sz' };
|
|
572
555
|
const MUTUAL_QUOTA_CODE_CHANNELS = { '001': 'sh_stock_connect', '002': 'hk_connect_sh', '003': 'sz_stock_connect', '004': 'hk_connect_sz' };
|
|
573
556
|
function parseMutualQuotaRow(raw) {
|
|
@@ -607,11 +590,7 @@ async function executeMutualQuota(params, signal) {
|
|
|
607
590
|
const parsed = requireRows(result, 'mutual quota');
|
|
608
591
|
return { data: { item: parsed.rows.map(parseMutualQuotaRow), pagination: pagination(1, 10, parsed.pages, parsed.total) }, schema: datacenterOutput(mutualQuotaRow) };
|
|
609
592
|
}
|
|
610
|
-
/**
|
|
611
|
-
* 个股主力资金快照。上游 `RPT_DMSK_TS_STOCKNEW` 实测**只保留最近一个交易日**的全市场一行一股
|
|
612
|
-
* (count=5199、TRADE_DATE 单一;带旧日期过滤器返回 9201 空),所以这不是时序能力:
|
|
613
|
-
* 契约里必须写死"按日期循环取不到历史",否则模型会拿它当序列反复请求。
|
|
614
|
-
*/
|
|
593
|
+
/** Main-capital snapshot: latest trading day only, one row per stock (5199, single TRADE_DATE); older dates return 9201. */
|
|
615
594
|
const MAIN_CAPITAL_SORTS = {
|
|
616
595
|
main_net_inflow: 'PRIME_INFLOW',
|
|
617
596
|
change_pct: 'CHANGE_RATE',
|
|
@@ -667,8 +646,7 @@ function parseMainCapitalRow(raw) {
|
|
|
667
646
|
const tradeDate = String(raw.TRADE_DATE ?? '').slice(0, 10);
|
|
668
647
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(tradeDate))
|
|
669
648
|
throw sourceError('Eastmoney main capital row has invalid TRADE_DATE', 'eastmoney_invalid_response');
|
|
670
|
-
// SECUCODE
|
|
671
|
-
// 股票的数据挂到另一只上。缺了就说明上游形状变了,响亮失败。
|
|
649
|
+
// SECUCODE always has a market suffix; never guess from the first digit. A missing suffix is a shape change.
|
|
672
650
|
if (typeof raw.SECUCODE !== 'string' || !raw.SECUCODE.includes('.')) {
|
|
673
651
|
throw sourceError('Eastmoney main capital row lost its market suffix in SECUCODE', 'eastmoney_invalid_response');
|
|
674
652
|
}
|
|
@@ -719,12 +697,7 @@ async function executeMainCapital(params, signal) {
|
|
|
719
697
|
const parsed = requireRows(result, 'main capital snapshot');
|
|
720
698
|
return { data: { item: parsed.rows.map(parseMainCapitalRow), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(mainCapitalRow) };
|
|
721
699
|
}
|
|
722
|
-
/**
|
|
723
|
-
* 分红送配方案:一行一个方案,按除权除息日过滤。`PRETAX_BONUS_RMB` / `BONUS_RATIO` / `IT_RATIO`
|
|
724
|
-
* 的"每 10 股"标度由上游自己写在 `IMPL_PLAN_PROFILE` 原文里(实测 `10派280.2423元(含税)` 配
|
|
725
|
-
* `PRETAX_BONUS_RMB=280.2423`、`10送4.00派3.00元` 配 `BONUS_RATIO=4`),所以照原值给出并保留原文。
|
|
726
|
-
* `IS_KCB` 与 `PUBLISH_DATE` 实测 200/200 全为 null,不收录(描述里点名,免得当成取数失败)。
|
|
727
|
-
*/
|
|
700
|
+
/** Dividend plans: per-10-share scale comes from `IMPL_PLAN_PROFILE` text; values kept as-is. `IS_KCB`/`PUBLISH_DATE` always null. */
|
|
728
701
|
const dividendRow = {
|
|
729
702
|
type: 'object',
|
|
730
703
|
properties: {
|
|
@@ -751,7 +724,6 @@ const dividendRow = {
|
|
|
751
724
|
},
|
|
752
725
|
additionalProperties: true,
|
|
753
726
|
};
|
|
754
|
-
/** 日期键归一:`2026-06-26 00:00:00` → `2026-06-26`;空值保留 null(12/200 行没有股权登记日)。 */
|
|
755
727
|
function dayValue(value) {
|
|
756
728
|
const text = String(value ?? '').slice(0, 10);
|
|
757
729
|
return /^\d{4}-\d{2}-\d{2}$/.test(text) ? text : null;
|
|
@@ -763,8 +735,7 @@ function parseDividendRow(raw) {
|
|
|
763
735
|
const exDate = dayValue(raw.EX_DIVIDEND_DATE);
|
|
764
736
|
if (exDate === null)
|
|
765
737
|
throw sourceError('Eastmoney dividend row has invalid EX_DIVIDEND_DATE', 'eastmoney_invalid_response');
|
|
766
|
-
//
|
|
767
|
-
// equity_record_date 实测 12/200 为 null(方案还没定登记日),那一列才允许 null。
|
|
738
|
+
// These three are always populated; equity_record_date may be null until the plan sets one.
|
|
768
739
|
const requiredDates = ['REPORT_DATE', 'PLAN_NOTICE_DATE', 'NOTICE_DATE'].map((key) => {
|
|
769
740
|
const day = dayValue(raw[key]);
|
|
770
741
|
if (day === null)
|
|
@@ -821,9 +792,8 @@ async function executeDividend(params, signal) {
|
|
|
821
792
|
return { data: { item: parsed.rows.map(parseDividendRow), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(dividendRow) };
|
|
822
793
|
}
|
|
823
794
|
/**
|
|
824
|
-
*
|
|
825
|
-
* `
|
|
826
|
-
* 新上市标的 `PRE_HOLDER_NUM=0` 且 `HOLDER_NUM_RATIO=null`(无上期可比),不是数据错误。
|
|
795
|
+
* Holder-count snapshot: `END_DATE` is the report period; `HOLD_NOTICE_DATE` is the disclosure date.
|
|
796
|
+
* New listings have `PRE_HOLDER_NUM=0` and null ratio (no prior period).
|
|
827
797
|
*/
|
|
828
798
|
const holderRow = {
|
|
829
799
|
type: 'object',
|
|
@@ -907,13 +877,8 @@ async function executeHolder(params, signal) {
|
|
|
907
877
|
return { data: { item: parsed.rows.map(parseHolderRow), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(holderRow) };
|
|
908
878
|
}
|
|
909
879
|
/**
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
913
|
-
* 口径靠算术核对,不靠记忆(实测 600519,2026-09-30):`RZRQYE = RZYE + RQYE`、
|
|
914
|
-
* `RZRQYECZ = RZYE - RQYE`、`RZJME = RZMRE - RZCHE`、`RQJMG = RQMCL - RQCHL` 四式精确成立,
|
|
915
|
-
* `RZYEZB = RZYE / SZ`(0.8617% 对得上),所以这几列的含义可以写进契约。
|
|
916
|
-
* 披露新鲜度**跨市场不一致**:实测沪市已到 2026-09-30,深市(000001)最新只有 2026-09-29。
|
|
880
|
+
* Margin detail: verified identities RZRQYE=RZYE+RQYE, RZRQYECZ=RZYE-RQYE, RZJME=RZMRE-RZCHE,
|
|
881
|
+
* RQJMG=RQMCL-RQCHL. Disclosure freshness differs by market (SH one day ahead of SZ).
|
|
917
882
|
*/
|
|
918
883
|
const MARGIN_COLUMNS = [
|
|
919
884
|
['RZYE', 'margin_balance'], ['RQYE', 'short_balance'], ['RZRQYE', 'total_balance'], ['RZRQYECZ', 'balance_gap'],
|
|
@@ -943,8 +908,7 @@ function parseMarginRow(raw, requestedThscode) {
|
|
|
943
908
|
const day = dayValue(raw.DATE);
|
|
944
909
|
if (day === null)
|
|
945
910
|
throw sourceError('Eastmoney margin row has invalid DATE', 'eastmoney_invalid_response');
|
|
946
|
-
// SCODE
|
|
947
|
-
// 再与上游 SECUCODE 对照,对不上就是上游把明细串行了。
|
|
911
|
+
// SCODE has no market suffix, so identity comes from the requested ticker; a mismatched SECUCODE is a row mix-up.
|
|
948
912
|
const secucode = typeof raw.SECUCODE === 'string' ? raw.SECUCODE : '';
|
|
949
913
|
if (secucode && secucode !== requestedThscode) {
|
|
950
914
|
throw sourceError(`Eastmoney margin row SECUCODE ${secucode} does not match requested ${requestedThscode}`, 'eastmoney_invalid_response');
|
|
@@ -982,15 +946,7 @@ async function executeMargin(params, signal) {
|
|
|
982
946
|
const thscode = String(params.ticker);
|
|
983
947
|
return { data: { item: parsed.rows.map((raw) => parseMarginRow(raw, thscode)), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(marginRow) };
|
|
984
948
|
}
|
|
985
|
-
/**
|
|
986
|
-
* 可转债发行清单(`RPT_BOND_CB_LIST`,实测全库 1059 只、起息日 2007-07-02 起)。
|
|
987
|
-
*
|
|
988
|
-
* 72 列里只有**条款与日期**有值:`CONVERT_STOCK_PRICE` / `CURRENT_BOND_PRICE`(_NEW) /
|
|
989
|
-
* `TRANSFER_PRICE` / `TRANSFER_PREMIUM_RATIO` / `RESALE_TRIG_PRICE` / `REDEEM_TRIG_PRICE` /
|
|
990
|
-
* `PBV_RATIO` / `MARKET` / `CONVERT_STOCK_PRICEHQ` 实测 **1059/1059 恒为 null**(东财页面上那些
|
|
991
|
-
* 行情数是另一条接口现算的),`TRANSFER_VALUE` 只是 `INITIAL_TRANSFER_PRICE` 的副本(1051/1059 相同
|
|
992
|
-
* 却多 8 个 null)。所以这张表**不承诺行情**,也不把那些 null 当成取数失败。
|
|
993
|
-
*/
|
|
949
|
+
/** Bond list: only terms/dates are populated; price and premium columns are always null (computed elsewhere). */
|
|
994
950
|
const BOND_MARKET_SUFFIX = { CNSESH: 'SH', CNSESZ: 'SZ', STAS00: null };
|
|
995
951
|
function bondStockIdentity(raw) {
|
|
996
952
|
const digits = String(raw.CONVERT_STOCK_CODE ?? '');
|
|
@@ -999,9 +955,7 @@ function bondStockIdentity(raw) {
|
|
|
999
955
|
return {
|
|
1000
956
|
stock_ticker: digits,
|
|
1001
957
|
stock_name: String(raw.SECURITY_SHORT_NAME ?? ''),
|
|
1002
|
-
//
|
|
1003
|
-
// (首位 1 也不等于沪),老三板退市债(STAS00)的正股 400266 更不是北交所标的——猜错就是把一只证券
|
|
1004
|
-
// 挂到另一个市场名下。未知市场一律不给后缀(`stock_thscode` 为 null),不猜也不报错。
|
|
958
|
+
// Market only from TRADE_MARKET: 126-prefixed 2007–09 SH bonds and STAS00 stock 400266 would be misattributed. Unknown → null.
|
|
1005
959
|
stock_thscode: BOND_MARKET_SUFFIX[String(raw.TRADE_MARKET ?? '')] ? `${digits}.${BOND_MARKET_SUFFIX[String(raw.TRADE_MARKET ?? '')]}` : null,
|
|
1006
960
|
};
|
|
1007
961
|
}
|
|
@@ -1035,7 +989,6 @@ const bondRow = {
|
|
|
1035
989
|
additionalProperties: true,
|
|
1036
990
|
};
|
|
1037
991
|
function parseBondRow(raw) {
|
|
1038
|
-
// 债券身份只认上游给的 SECUCODE(带 .SH/.SZ/.NQ 后缀):本表 1059/1059 都有值,丢了后缀就是形状变了。
|
|
1039
992
|
if (typeof raw.SECUCODE !== 'string' || !raw.SECUCODE.includes('.')) {
|
|
1040
993
|
throw sourceError('Eastmoney convertible bond row lost its market suffix in SECUCODE', 'eastmoney_invalid_response');
|
|
1041
994
|
}
|
|
@@ -1075,7 +1028,7 @@ function parseBondRow(raw) {
|
|
|
1075
1028
|
in_conversion_period: textOrNull(raw.IS_CONVERT_STOCK),
|
|
1076
1029
|
};
|
|
1077
1030
|
}
|
|
1078
|
-
/**
|
|
1031
|
+
/** Strip .SH/.SZ/.NQ suffixes; never infer a market. */
|
|
1079
1032
|
function bondCode(value, name) {
|
|
1080
1033
|
const raw = String(value).toUpperCase();
|
|
1081
1034
|
const digits = raw.replace(/\.[A-Z]{2,3}$/u, '');
|
|
@@ -1100,8 +1053,7 @@ async function executeBondList(params, signal) {
|
|
|
1100
1053
|
url.searchParams.set('columns', 'ALL');
|
|
1101
1054
|
url.searchParams.set('source', 'WEB');
|
|
1102
1055
|
url.searchParams.set('client', 'WEB');
|
|
1103
|
-
//
|
|
1104
|
-
// 只按其一是因为日期区间过滤实测只有这一列**从不为空**——LISTING_DATE 有 8 只未上市的债会被区间静默吞掉。
|
|
1056
|
+
// Use PUBLIC_START_DATE: the only date filter never null (equals VALUE_DATE); LISTING_DATE would drop 8 unlisted bonds.
|
|
1105
1057
|
url.searchParams.set('sortColumns', 'PUBLIC_START_DATE');
|
|
1106
1058
|
url.searchParams.set('sortTypes', '-1');
|
|
1107
1059
|
url.searchParams.set('pageNumber', String(params.page));
|
|
@@ -1,37 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* FTShare
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - 同花顺 `capital-flow/*` 对我们是 `code=2004` 永久关闭,东财 `eastmoney_main_capital_snapshot`
|
|
7
|
-
* 实测**只有查询日一股一行**——个股资金流的历史序列在本仓一直是空的。这一族给到最近约 45 个交易日
|
|
8
|
-
* (T-45 仍有 5199 行、T-50 起 total=0),主力五档更给到约 187 个交易日。
|
|
9
|
-
* - 板块(行业)资金滚动此前只有 `eastmoney_cashflow_rotation` 一条,上游一抖就没有第二家。
|
|
10
|
-
* - 北交所行情此前是**零**:腾讯那一路在参数层明确拒 BJ,而这一族实测含 349 只北证票。
|
|
11
|
-
* - 期货与贵金属此前在本仓是**整块空白**(`ticker_search` 只允许消歧到期货交易所,没有一条数据端点),
|
|
12
|
-
* 所以批次 F 那十颗不是第二家,而是第一家:合约清单与要素、日/周/月/季 K 线、涨跌停价、每日结算参数、
|
|
13
|
-
* 仓单日报、品种周报、南华商品指数、东财会员持仓排名、贵金属价格。
|
|
14
|
-
* 上游分别是同花顺与同花顺 daec,与现有能力**不是同一条抓取路径**,才够称 plan B。
|
|
15
|
-
*
|
|
16
|
-
* ⛔ 本模块的 description 与注释**只允许写有原始报文可回查的数字**。2026-10-15 曾有一批"实测计数"
|
|
17
|
-
* (行数在 3766/3864/5450/5574 之间跳、`ys` 返回 309 行、业务码 100605、页码回显谎报)事后逐条回查
|
|
18
|
-
* **在任何一次真报文里都不存在**,是我把不同接口的读数串成了因果;据此撤掉的两颗能力已于当晚复验注册回来。
|
|
19
|
-
* 教训写进 `docs/design/ftshare-source-survey.md` §7.5–§7.7:**没打进日志的数字不算实测**。
|
|
20
|
-
*
|
|
21
|
-
* ⛔ 免费档限流 **10 QPS / burst 20**(官方口径)。`DataCollectorHub` 的 FIFO 只保证同一时刻
|
|
22
|
-
* 一个请求,**不含最小间隔**——串行 ≠ 节流(`src/net/eastmoney-client.ts` 同一条教训)。
|
|
23
|
-
* 本模块用进程级节流链把上限压到 ~8 QPS,留突发余量。
|
|
24
|
-
*
|
|
25
|
-
* ⛔ 凭据一律**调用期解析**,注册只看结构(AGENTS.md §9.7,2026-09-30 桌面端 61 能力静默缺席
|
|
26
|
-
* 那一族事故)。套餐外路由返回 403 `route_not_allowed`,归类成 `plan_closed` 且不重试——
|
|
27
|
-
* 它和 Fuyao 的 `2004` 是同一件事:能力级不可用,重试只白烧回合。
|
|
2
|
+
* FTShare (非凸科技 `market.ft.tech/gateway`): capital flows, quote/K-line, calendars, block trades,
|
|
3
|
+
* SW industry, futures/metals; the only source for A-share flow history, BJ quotes and futures.
|
|
4
|
+
* ⛔ Only numbers verifiable in a logged raw response; free tier 10 QPS/burst 20 (we cap ~8 QPS).
|
|
5
|
+
* ⛔ Credentials resolve per call; 403 route_not_allowed = plan_closed, never retry (cf. Fuyao 2004).
|
|
28
6
|
*/
|
|
29
7
|
import type { Context } from '@deepseek-ai/cordis';
|
|
30
8
|
import type { DataSource } from '../data-collector/hub.js';
|
|
31
9
|
type JsonRecord = Record<string, unknown>;
|
|
32
|
-
/** 10 QPS 的 80% 上限:120ms 最小间隔 ⇒ 稳态 ~8.3 次/秒,突发也不会越过 20。 */
|
|
33
10
|
export declare const FTSHARE_MIN_INTERVAL_MS = 120;
|
|
34
|
-
/** 失败归类:与 `FuyaoFailureKind` 同一套语汇,`plan_closed` 对应 Fuyao 的 `capability_closed`。 */
|
|
35
11
|
type FtshareFailureKind = 'credential_missing' | 'rate_limited' | 'plan_closed' | 'invalid_parameter' | 'http_error' | 'upstream_error' | 'invalid_response';
|
|
36
12
|
export declare class FtshareError extends Error {
|
|
37
13
|
readonly kind: FtshareFailureKind;
|
|
@@ -45,13 +21,9 @@ export declare class FtshareError extends Error {
|
|
|
45
21
|
export interface FtshareRequester {
|
|
46
22
|
getJson(path: string, search: URLSearchParams, signal: AbortSignal): Promise<JsonRecord>;
|
|
47
23
|
}
|
|
48
|
-
/**
|
|
24
|
+
/** Key resolved per call; constructing the client touches no credentials. */
|
|
49
25
|
export declare function createFtshareClient(resolveApiKey: () => Promise<string | undefined>, baseUrl?: string): FtshareRequester;
|
|
50
|
-
/**
|
|
51
|
-
* key 每次调用现取(与 `resolveFuyaoApiKey` 同一口径):credentials 优先、环境变量兜底。
|
|
52
|
-
* ⛔ 注册面**不得**以"装配期读到 key"为条件(AGENTS.md §9.7),也不要把这里的 rejection 缓存住——
|
|
53
|
-
* 一次装配期抢跑会把这个能力钉到进程结束。
|
|
54
|
-
*/
|
|
26
|
+
/** Credentials first, env fallback; never gate registration on key availability or cache a rejection. */
|
|
55
27
|
export declare function resolveFtshareApiKey(ctx: Context, credentialRef?: string): Promise<string | undefined>;
|
|
56
28
|
export declare function createFtshareSources(resolveApiKey: () => Promise<string | undefined>): DataSource[];
|
|
57
29
|
export {};
|