pi-provider-kimi-code 0.5.1 → 0.6.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 +94 -46
- package/index.ts +310 -110
- package/package.json +8 -8
- package/src/config.ts +475 -72
- package/src/constants.ts +29 -11
- package/src/device.ts +14 -0
- package/src/models.ts +115 -51
- package/src/payload.ts +90 -47
- package/src/schema-dedup.ts +256 -0
- package/src/stream.ts +70 -17
- package/src/tools/common.ts +115 -0
- package/src/tools/datasource.ts +239 -0
- package/src/tools/moonshot.ts +22 -111
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type, type Static } from "@earendil-works/pi-ai";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
type BuildKimiToolOptions,
|
|
6
|
+
buildHeaders,
|
|
7
|
+
getKimiDatasourceUrl,
|
|
8
|
+
buildKimiToolDeps,
|
|
9
|
+
buildTimeoutSignal,
|
|
10
|
+
errorResult,
|
|
11
|
+
fetchWithAuthRetry,
|
|
12
|
+
firstText,
|
|
13
|
+
readErrorBody,
|
|
14
|
+
shouldCollapse,
|
|
15
|
+
textComponent,
|
|
16
|
+
} from "./common.ts";
|
|
17
|
+
|
|
18
|
+
const DATASOURCE_DEFINITIONS = [
|
|
19
|
+
{
|
|
20
|
+
name: "stock_finance_data",
|
|
21
|
+
description: "A/HK/US stocks, prices, financial reports, indicators, shareholders.",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "yahoo_finance",
|
|
25
|
+
description: "Global finance: quotes, analyst ratings, options, indexes, historical prices.",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "world_bank_open_data",
|
|
29
|
+
description: "World Bank macro data: GDP, inflation, population, country comparisons.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "tianyancha",
|
|
33
|
+
description: "Chinese enterprise registry: shareholders, legal representative, risks, patents.",
|
|
34
|
+
},
|
|
35
|
+
{ name: "arxiv", description: "arXiv preprints: paper search, abstracts, arXiv IDs." },
|
|
36
|
+
{
|
|
37
|
+
name: "scholar",
|
|
38
|
+
description: "Google Scholar: academic papers, authors, citations, high-citation surveys.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "yuandian_law",
|
|
42
|
+
description: "Chinese laws, regulations, judicial cases; labor contracts and legal clauses.",
|
|
43
|
+
},
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
const DATASOURCE_NAMES = DATASOURCE_DEFINITIONS.map((source) => source.name);
|
|
47
|
+
|
|
48
|
+
export const kimiDatasourceSchema = Type.Object({
|
|
49
|
+
data_source_name: Type.Union(
|
|
50
|
+
DATASOURCE_NAMES.map((name) => Type.Literal(name)),
|
|
51
|
+
{
|
|
52
|
+
description:
|
|
53
|
+
"Source: stock_finance_data stocks/reports; yahoo_finance quotes/options; world_bank_open_data GDP/population; tianyancha Chinese company registry/legal rep; arxiv preprints; scholar citations; yuandian_law Chinese laws/cases/contracts.",
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
api_name: Type.Optional(
|
|
57
|
+
Type.String({
|
|
58
|
+
description: "API name from the datasource docs. Omit to get docs first.",
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
61
|
+
params: Type.Optional(
|
|
62
|
+
Type.Record(Type.String(), Type.Unknown(), {
|
|
63
|
+
description: "Parameters matching the selected datasource API docs.",
|
|
64
|
+
}),
|
|
65
|
+
),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export type KimiDatasourceParams = Static<typeof kimiDatasourceSchema>;
|
|
69
|
+
|
|
70
|
+
interface DatasourceToolResponse {
|
|
71
|
+
is_success?: boolean;
|
|
72
|
+
error?: unknown;
|
|
73
|
+
result?: {
|
|
74
|
+
assistant?: unknown;
|
|
75
|
+
user?: unknown;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function extractTextFromResult(result: unknown): string {
|
|
80
|
+
if (!Array.isArray(result)) return "";
|
|
81
|
+
const texts: string[] = [];
|
|
82
|
+
for (const item of result) {
|
|
83
|
+
if (typeof item === "string") {
|
|
84
|
+
texts.push(item);
|
|
85
|
+
} else if (typeof item === "object" && item !== null) {
|
|
86
|
+
const record = item as Record<string, unknown>;
|
|
87
|
+
if (typeof record.text === "string") {
|
|
88
|
+
texts.push(record.text);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return texts.join("\n");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extractResponseText(data: DatasourceToolResponse): string {
|
|
96
|
+
if (data.result?.assistant) {
|
|
97
|
+
const text = extractTextFromResult(data.result.assistant);
|
|
98
|
+
if (text) return text;
|
|
99
|
+
}
|
|
100
|
+
if (data.result?.user) {
|
|
101
|
+
const text = extractTextFromResult(data.result.user);
|
|
102
|
+
if (text) return text;
|
|
103
|
+
}
|
|
104
|
+
return JSON.stringify(data, null, 2);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function extractResponseFiles(data: DatasourceToolResponse): string[] {
|
|
108
|
+
const raw = data as Record<string, unknown>;
|
|
109
|
+
if (!Array.isArray(raw.files)) return [];
|
|
110
|
+
const names: string[] = [];
|
|
111
|
+
for (const file of raw.files) {
|
|
112
|
+
if (
|
|
113
|
+
typeof file === "object" &&
|
|
114
|
+
file !== null &&
|
|
115
|
+
typeof (file as Record<string, unknown>).name === "string"
|
|
116
|
+
) {
|
|
117
|
+
names.push((file as Record<string, unknown>).name as string);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return names;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const REQUEST_ID_HEADERS = [
|
|
124
|
+
"x-request-id",
|
|
125
|
+
"x-trace-id",
|
|
126
|
+
"x-msh-request-id",
|
|
127
|
+
"x-msh-trace-id",
|
|
128
|
+
"request-id",
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
function extractRequestId(headers: Headers): string | undefined {
|
|
132
|
+
for (const key of REQUEST_ID_HEADERS) {
|
|
133
|
+
const value = headers.get(key);
|
|
134
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildTraceLine(toolCallId: string, requestId: string | undefined): string {
|
|
140
|
+
const parts: string[] = [];
|
|
141
|
+
if (requestId) parts.push(`request-id: ${requestId}`);
|
|
142
|
+
parts.push(`tool-call-id: ${toolCallId}`);
|
|
143
|
+
return `\n\n[kimi-datasource] ${parts.join(" · ")}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function buildKimiDatasourceTool(options: BuildKimiToolOptions = {}) {
|
|
147
|
+
const deps = buildKimiToolDeps(options);
|
|
148
|
+
const defaultCollapsed = options.defaultCollapsed ?? true;
|
|
149
|
+
|
|
150
|
+
return defineTool({
|
|
151
|
+
name: "kimi_datasource",
|
|
152
|
+
label: "Kimi Datasource",
|
|
153
|
+
description:
|
|
154
|
+
"Call Kimi datasource APIs. Covers stocks/finance, macro data, Chinese company registry, papers, and Chinese law/cases/contracts. First omit api_name to get docs, then call api_name with documented params.",
|
|
155
|
+
promptSnippet: "Query Kimi datasource APIs; get docs first, then call documented api_name",
|
|
156
|
+
parameters: kimiDatasourceSchema,
|
|
157
|
+
|
|
158
|
+
async execute(toolCallId, params, signal) {
|
|
159
|
+
const accessToken = deps.getAccessToken();
|
|
160
|
+
if (!accessToken) {
|
|
161
|
+
return errorResult<string>(
|
|
162
|
+
"Missing Kimi Code OAuth credentials. Run /login kimi-coding first.",
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const timeout = buildTimeoutSignal(signal);
|
|
167
|
+
try {
|
|
168
|
+
const apiName = params.api_name?.trim();
|
|
169
|
+
const body = apiName
|
|
170
|
+
? {
|
|
171
|
+
method: "call_data_source_tool",
|
|
172
|
+
params: {
|
|
173
|
+
data_source_name: params.data_source_name,
|
|
174
|
+
api_name: apiName,
|
|
175
|
+
params: params.params ?? {},
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
: {
|
|
179
|
+
method: "get_data_source_desc",
|
|
180
|
+
params: { name: params.data_source_name },
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const response = await fetchWithAuthRetry(deps, accessToken, (token) =>
|
|
184
|
+
deps.fetch(getKimiDatasourceUrl(), {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: buildHeaders(token, toolCallId),
|
|
187
|
+
body: JSON.stringify(body),
|
|
188
|
+
signal: timeout.signal,
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const requestId = extractRequestId(response.headers);
|
|
193
|
+
const trace = buildTraceLine(toolCallId, requestId);
|
|
194
|
+
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
const bodyText = await readErrorBody(response);
|
|
197
|
+
return errorResult<string>(
|
|
198
|
+
`kimi_datasource failed: ${response.status}${bodyText ? ` ${bodyText}` : ""}${trace}`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const data = (await response.json()) as DatasourceToolResponse;
|
|
203
|
+
if (data.is_success === false) {
|
|
204
|
+
const errorText =
|
|
205
|
+
typeof data.error === "string"
|
|
206
|
+
? data.error
|
|
207
|
+
: JSON.stringify(data.error ?? data, null, 2);
|
|
208
|
+
return errorResult<string>(`kimi_datasource error: ${errorText}${trace}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let text = extractResponseText(data);
|
|
212
|
+
const files = extractResponseFiles(data);
|
|
213
|
+
if (files.length > 0) {
|
|
214
|
+
text += `\n\nNote: the backend returned ${files.length} file(s) that cannot be written in this context: ${files.join(", ")}`;
|
|
215
|
+
}
|
|
216
|
+
text += trace;
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: "text", text }],
|
|
220
|
+
details: text,
|
|
221
|
+
};
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
224
|
+
return errorResult<string>(`kimi_datasource failed: ${message}`);
|
|
225
|
+
} finally {
|
|
226
|
+
timeout.cleanup();
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
renderResult(result, renderOptions) {
|
|
231
|
+
const text = firstText(result);
|
|
232
|
+
if (!shouldCollapse(defaultCollapsed, renderOptions.expanded)) {
|
|
233
|
+
return textComponent(text);
|
|
234
|
+
}
|
|
235
|
+
const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text;
|
|
236
|
+
return textComponent(`kimi_datasource: ${text.length} chars\n${preview}`);
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
}
|
package/src/tools/moonshot.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineTool, type AgentToolResult } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type, type Static } from "@earendil-works/pi-ai";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import {
|
|
5
|
+
type BuildKimiToolOptions,
|
|
6
|
+
buildHeaders,
|
|
7
|
+
getKimiBaseV1,
|
|
8
|
+
buildKimiToolDeps,
|
|
9
|
+
buildTimeoutSignal,
|
|
10
|
+
errorResult,
|
|
11
|
+
fetchWithAuthRetry,
|
|
12
|
+
firstText,
|
|
13
|
+
readErrorBody,
|
|
14
|
+
shouldCollapse,
|
|
15
|
+
textComponent,
|
|
16
|
+
} from "./common.ts";
|
|
10
17
|
|
|
11
18
|
export const moonshotSearchSchema = Type.Object({
|
|
12
19
|
query: Type.String({ description: "The query text to search for." }),
|
|
@@ -45,76 +52,13 @@ export interface MoonshotFetchResult {
|
|
|
45
52
|
content: string;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
interface MoonshotToolDeps {
|
|
49
|
-
fetch: typeof fetch;
|
|
50
|
-
getAccessToken: () => string | null;
|
|
51
|
-
refreshAccessToken: (currentToken: string) => Promise<string | null>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface BuildMoonshotToolOptions {
|
|
55
|
-
deps?: Partial<MoonshotToolDeps>;
|
|
56
|
-
defaultCollapsed?: boolean;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface MoonshotSearchResponse {
|
|
60
|
-
search_results?: unknown;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function defaultGetAccessToken(): string | null {
|
|
64
|
-
const credential = AuthStorage.create().get(PROVIDER_ID);
|
|
65
|
-
if (!credential || credential.type !== "oauth") return null;
|
|
66
|
-
return credential.access;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function buildDeps(options: BuildMoonshotToolOptions = {}): MoonshotToolDeps {
|
|
70
|
-
return {
|
|
71
|
-
fetch: options.deps?.fetch ?? fetch,
|
|
72
|
-
getAccessToken: options.deps?.getAccessToken ?? defaultGetAccessToken,
|
|
73
|
-
refreshAccessToken: options.deps?.refreshAccessToken ?? refreshKimiAuthToken,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
55
|
function clampLimit(value: number | undefined): number {
|
|
78
56
|
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
79
57
|
return Math.max(1, Math.min(20, Math.trunc(value)));
|
|
80
58
|
}
|
|
81
59
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
content: [{ type: "text", text: message }],
|
|
85
|
-
details: undefined as T,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function buildTimeoutSignal(signal: AbortSignal | undefined): {
|
|
90
|
-
signal: AbortSignal;
|
|
91
|
-
cleanup: () => void;
|
|
92
|
-
} {
|
|
93
|
-
const controller = new AbortController();
|
|
94
|
-
const timeout = setTimeout(() => controller.abort(), MOONSHOT_TIMEOUT_MS);
|
|
95
|
-
const abort = () => controller.abort();
|
|
96
|
-
if (signal?.aborted) {
|
|
97
|
-
controller.abort();
|
|
98
|
-
} else {
|
|
99
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
signal: controller.signal,
|
|
104
|
-
cleanup: () => {
|
|
105
|
-
clearTimeout(timeout);
|
|
106
|
-
signal?.removeEventListener("abort", abort);
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function buildHeaders(accessToken: string, toolCallId: string): Record<string, string> {
|
|
112
|
-
return {
|
|
113
|
-
...getCommonHeaders(),
|
|
114
|
-
Authorization: `Bearer ${accessToken}`,
|
|
115
|
-
"Content-Type": "application/json",
|
|
116
|
-
"X-Msh-Tool-Call-Id": toolCallId,
|
|
117
|
-
};
|
|
60
|
+
interface MoonshotSearchResponse {
|
|
61
|
+
search_results?: unknown;
|
|
118
62
|
}
|
|
119
63
|
|
|
120
64
|
function stringField(record: Record<string, unknown>, key: string): string {
|
|
@@ -144,22 +88,6 @@ function searchResultsText(results: MoonshotSearchResult[]): string {
|
|
|
144
88
|
return JSON.stringify(results, null, 2);
|
|
145
89
|
}
|
|
146
90
|
|
|
147
|
-
function textComponent(text: string) {
|
|
148
|
-
return {
|
|
149
|
-
render: () => text.split("\n"),
|
|
150
|
-
invalidate: () => {},
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function firstText(result: AgentToolResult<unknown>): string {
|
|
155
|
-
const first = result.content[0];
|
|
156
|
-
return first?.type === "text" ? first.text : "";
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function shouldCollapse(defaultCollapsed: boolean, expanded: boolean | undefined): boolean {
|
|
160
|
-
return defaultCollapsed && expanded !== true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
91
|
function renderSearchResult(
|
|
164
92
|
result: AgentToolResult<MoonshotSearchResult[]>,
|
|
165
93
|
expanded: boolean | undefined,
|
|
@@ -195,25 +123,8 @@ function renderFetchResult(
|
|
|
195
123
|
);
|
|
196
124
|
}
|
|
197
125
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function fetchWithAuthRetry(
|
|
203
|
-
deps: MoonshotToolDeps,
|
|
204
|
-
accessToken: string,
|
|
205
|
-
request: (token: string) => Promise<Response>,
|
|
206
|
-
): Promise<Response> {
|
|
207
|
-
const response = await request(accessToken);
|
|
208
|
-
if (response.status !== 401) return response;
|
|
209
|
-
|
|
210
|
-
const refreshed = await deps.refreshAccessToken(accessToken);
|
|
211
|
-
if (!refreshed || refreshed === accessToken) return response;
|
|
212
|
-
return request(refreshed);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export function buildMoonshotSearchTool(options: BuildMoonshotToolOptions = {}) {
|
|
216
|
-
const deps = buildDeps(options);
|
|
126
|
+
export function buildMoonshotSearchTool(options: BuildKimiToolOptions = {}) {
|
|
127
|
+
const deps = buildKimiToolDeps(options);
|
|
217
128
|
const defaultCollapsed = options.defaultCollapsed ?? true;
|
|
218
129
|
|
|
219
130
|
return defineTool({
|
|
@@ -236,7 +147,7 @@ export function buildMoonshotSearchTool(options: BuildMoonshotToolOptions = {})
|
|
|
236
147
|
const timeout = buildTimeoutSignal(signal);
|
|
237
148
|
try {
|
|
238
149
|
const response = await fetchWithAuthRetry(deps, accessToken, (token) =>
|
|
239
|
-
deps.fetch(`${
|
|
150
|
+
deps.fetch(`${getKimiBaseV1()}/search`, {
|
|
240
151
|
method: "POST",
|
|
241
152
|
headers: buildHeaders(token, toolCallId),
|
|
242
153
|
body: JSON.stringify({
|
|
@@ -276,8 +187,8 @@ export function buildMoonshotSearchTool(options: BuildMoonshotToolOptions = {})
|
|
|
276
187
|
});
|
|
277
188
|
}
|
|
278
189
|
|
|
279
|
-
export function buildMoonshotFetchTool(options:
|
|
280
|
-
const deps =
|
|
190
|
+
export function buildMoonshotFetchTool(options: BuildKimiToolOptions = {}) {
|
|
191
|
+
const deps = buildKimiToolDeps(options);
|
|
281
192
|
const defaultCollapsed = options.defaultCollapsed ?? true;
|
|
282
193
|
|
|
283
194
|
return defineTool({
|
|
@@ -298,7 +209,7 @@ export function buildMoonshotFetchTool(options: BuildMoonshotToolOptions = {}) {
|
|
|
298
209
|
const timeout = buildTimeoutSignal(signal);
|
|
299
210
|
try {
|
|
300
211
|
const response = await fetchWithAuthRetry(deps, accessToken, (token) =>
|
|
301
|
-
deps.fetch(`${
|
|
212
|
+
deps.fetch(`${getKimiBaseV1()}/fetch`, {
|
|
302
213
|
method: "POST",
|
|
303
214
|
headers: {
|
|
304
215
|
...buildHeaders(token, toolCallId),
|