@huaqiu/dsh-eda-host 0.4.5 → 0.4.7
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/lib/index.d.mts +19 -10
- package/lib/index.mjs +62 -5
- package/package.json +2 -2
- package/src/client.ts +23 -10
- package/src/config.ts +19 -0
- package/src/index.ts +28 -1
- package/src/tools.ts +40 -2
- package/src/types.ts +6 -5
package/lib/index.d.mts
CHANGED
|
@@ -18,6 +18,8 @@ interface EdaHostConfig {
|
|
|
18
18
|
netlistPathPrefix?: string;
|
|
19
19
|
/** Path prefix for PCB selection; default "/api/v1/pcb-selection". */
|
|
20
20
|
pcbSelectionPathPrefix?: string;
|
|
21
|
+
/** Path prefix for the complete-board query; default "/api/v1/pcb-board". */
|
|
22
|
+
pcbBoardPathPrefix?: string;
|
|
21
23
|
/** Path prefix for host discovery; default "/api/v1/host". */
|
|
22
24
|
hostPathPrefix?: string;
|
|
23
25
|
/**
|
|
@@ -71,12 +73,13 @@ interface SchematicNetlist {
|
|
|
71
73
|
nets: ElectricalNet[];
|
|
72
74
|
}
|
|
73
75
|
/**
|
|
74
|
-
* Semantic PCB
|
|
76
|
+
* Semantic PCB snapshot types.
|
|
75
77
|
*
|
|
76
78
|
* Self-contained structural mirror of `hq.pcb.v1` (the hq-edge-owned semantic
|
|
77
|
-
* protobuf contract for `
|
|
78
|
-
*
|
|
79
|
-
*
|
|
79
|
+
* protobuf contract for `PcbQueryService.GetSelection` / `GetBoard`). The
|
|
80
|
+
* same PcbSnapshot shape backs both the current selection and the complete
|
|
81
|
+
* board. Units follow the hq.pcb.v1 convention: positions/sizes/lengths in mm,
|
|
82
|
+
* rotations in degrees, `id` is the KiCad native object identity.
|
|
80
83
|
*/
|
|
81
84
|
interface PcbPoint {
|
|
82
85
|
x: number;
|
|
@@ -174,7 +177,7 @@ interface PcbGroup {
|
|
|
174
177
|
id: string;
|
|
175
178
|
itemIds: string[];
|
|
176
179
|
}
|
|
177
|
-
interface
|
|
180
|
+
interface PcbSnapshot {
|
|
178
181
|
footprints: PcbFootprint[];
|
|
179
182
|
pads: PcbPad[];
|
|
180
183
|
tracks: PcbTrack[];
|
|
@@ -294,11 +297,17 @@ interface EdaHostClient {
|
|
|
294
297
|
/** Capabilities the host currently provides. */
|
|
295
298
|
getEdaHostCapabilities(options?: EdaHostRequestOptions): Promise<EdaHostCapability[]>;
|
|
296
299
|
/**
|
|
297
|
-
* Semantic PCB
|
|
298
|
-
*
|
|
299
|
-
* selection resolves to an all-empty `
|
|
300
|
+
* Semantic PCB snapshot of the current PCB editor selection (hq.pcb.v1
|
|
301
|
+
* PcbQueryService.GetSelection bridged through hq-edge). An empty
|
|
302
|
+
* selection resolves to an all-empty `PcbSnapshot` — never an error.
|
|
300
303
|
*/
|
|
301
|
-
getPcbSelection(options?: EdaHostRequestOptions): Promise<
|
|
304
|
+
getPcbSelection(options?: EdaHostRequestOptions): Promise<PcbSnapshot>;
|
|
305
|
+
/**
|
|
306
|
+
* Semantic PCB snapshot of the complete board (hq.pcb.v1
|
|
307
|
+
* PcbQueryService.GetBoard bridged through hq-edge). An empty board
|
|
308
|
+
* resolves to an all-empty `PcbSnapshot` — never an error.
|
|
309
|
+
*/
|
|
310
|
+
getPcbBoard(options?: EdaHostRequestOptions): Promise<PcbSnapshot>;
|
|
302
311
|
}
|
|
303
312
|
/**
|
|
304
313
|
* Extract the semantic `SchematicNetlist` from an hq-edge netlist body.
|
|
@@ -373,4 +382,4 @@ declare module '@deepseek-ai/cordis' {
|
|
|
373
382
|
*/
|
|
374
383
|
declare function apply(ctx: Context, config?: Partial<EdaHostConfig>): () => void;
|
|
375
384
|
//#endregion
|
|
376
|
-
export { type EdaHostCapability, type EdaHostClient, type EdaHostConfig, type EdaHostExecutable, type EdaHostIdentity, type EdaHostInfo, type EdaHostInstallation, type EdaHostRequestOptions, type EdaHostType, type ElectricalNet, type ElectricalType, NetlistError, type NetlistErrorKind, type PcbArc, type PcbDimension, type PcbFootprint, type PcbGroup, type PcbNetRef, type PcbPad, type PcbPoint, type PcbSegment, type
|
|
385
|
+
export { type EdaHostCapability, type EdaHostClient, type EdaHostConfig, type EdaHostExecutable, type EdaHostIdentity, type EdaHostInfo, type EdaHostInstallation, type EdaHostRequestOptions, type EdaHostType, type ElectricalNet, type ElectricalType, NetlistError, type NetlistErrorKind, type PcbArc, type PcbDimension, type PcbFootprint, type PcbGroup, type PcbNetRef, type PcbPad, type PcbPoint, type PcbSegment, type PcbShape, type PcbSnapshot, type PcbText, type PcbTrack, type PcbVia, type PcbZone, type PinDefinition, type PinReference, type SchematicComponent, type SchematicNetlist, apply, inject, name, parseNetlistBody };
|
package/lib/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ function resolveEdaHostConfig(config, env = process.env) {
|
|
|
10
10
|
const baseUrl = config?.hqEdgeBaseUrl ?? env.HQ_EDGE_BASE_URL ?? "";
|
|
11
11
|
const pathPrefix = config?.netlistPathPrefix ?? env.HQ_EDGE_NETLIST_PATH ?? "/api/v1/netlist";
|
|
12
12
|
const pcbSelectionPrefix = config?.pcbSelectionPathPrefix ?? env.HQ_EDGE_PCB_SELECTION_PATH ?? "/api/v1/pcb-selection";
|
|
13
|
+
const pcbBoardPrefix = config?.pcbBoardPathPrefix ?? env.HQ_EDGE_PCB_BOARD_PATH ?? "/api/v1/pcb-board";
|
|
13
14
|
const hostPrefix = config?.hostPathPrefix ?? env.HQ_EDGE_HOST_PATH ?? "/api/v1/host";
|
|
14
15
|
const timeoutRaw = config?.requestTimeoutMs ?? env.HQ_EDGE_REQUEST_TIMEOUT_MS;
|
|
15
16
|
const timeout = Number.parseInt(String(timeoutRaw ?? ""), 10);
|
|
@@ -17,6 +18,7 @@ function resolveEdaHostConfig(config, env = process.env) {
|
|
|
17
18
|
hqEdgeBaseUrl: baseUrl,
|
|
18
19
|
netlistPathPrefix: pathPrefix,
|
|
19
20
|
pcbSelectionPathPrefix: pcbSelectionPrefix,
|
|
21
|
+
pcbBoardPathPrefix: pcbBoardPrefix,
|
|
20
22
|
hostPathPrefix: hostPrefix,
|
|
21
23
|
requestTimeoutMs: Number.isFinite(timeout) && timeout > 0 ? timeout : DEFAULT_REQUEST_TIMEOUT_MS
|
|
22
24
|
};
|
|
@@ -33,6 +35,10 @@ function netlistUrlOf(config, scope) {
|
|
|
33
35
|
function pcbSelectionUrlOf(config) {
|
|
34
36
|
return `${(config.hqEdgeBaseUrl ?? "").replace(/\/+$/, "")}/${(config.pcbSelectionPathPrefix ?? "/api/v1/pcb-selection").replace(/^\/+|\/+$/g, "")}`;
|
|
35
37
|
}
|
|
38
|
+
/** Build the absolute URL for the complete-board query route. */
|
|
39
|
+
function pcbBoardUrlOf(config) {
|
|
40
|
+
return `${(config.hqEdgeBaseUrl ?? "").replace(/\/+$/, "")}/${(config.pcbBoardPathPrefix ?? "/api/v1/pcb-board").replace(/^\/+|\/+$/g, "")}`;
|
|
41
|
+
}
|
|
36
42
|
const HOST_ROUTE = {
|
|
37
43
|
info: "/info",
|
|
38
44
|
capabilities: "/capabilities"
|
|
@@ -191,8 +197,8 @@ function createEdaHostClient(config, deps = {}) {
|
|
|
191
197
|
async function fetchScope(scope, options) {
|
|
192
198
|
return parseNetlistBody(await getJson(netlistUrlOf(resolveConfig(), scope), options, "netlist"));
|
|
193
199
|
}
|
|
194
|
-
function
|
|
195
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new NetlistError("INTERNAL",
|
|
200
|
+
function parsePcbSnapshot(value, what) {
|
|
201
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new NetlistError("INTERNAL", `eda-host: malformed ${what} response from hq-edge`);
|
|
196
202
|
const asArray = (v) => Array.isArray(v) ? v : [];
|
|
197
203
|
return {
|
|
198
204
|
footprints: asArray(value.footprints),
|
|
@@ -223,7 +229,10 @@ function createEdaHostClient(config, deps = {}) {
|
|
|
223
229
|
return body.capabilities.filter((c) => typeof c === "string");
|
|
224
230
|
},
|
|
225
231
|
getPcbSelection: async (options) => {
|
|
226
|
-
return
|
|
232
|
+
return parsePcbSnapshot(await getJson(pcbSelectionUrlOf(resolveConfig()), options, "pcb selection"), "PCB selection");
|
|
233
|
+
},
|
|
234
|
+
getPcbBoard: async (options) => {
|
|
235
|
+
return parsePcbSnapshot(await getJson(pcbBoardUrlOf(resolveConfig()), options, "pcb board"), "PCB board");
|
|
227
236
|
}
|
|
228
237
|
};
|
|
229
238
|
}
|
|
@@ -327,7 +336,7 @@ function createEdaHostTools(env) {
|
|
|
327
336
|
return [
|
|
328
337
|
defineTool({
|
|
329
338
|
name: "get_eda_host_info",
|
|
330
|
-
description: "Describe the EDA host currently connected through hq-edge (DSH → dsh-eda-host → hq-edge → EDA host). Returns { ok, info: { identity: { hostType, hostName, version }, installation: { applicationPath, executables[]: { name, path } } } }. Use it when you need factual information about the current EDA environment, such as \"what EDA host am I connected to\", \"what version is it\", \"where is it installed\", or \"where is kicad-cli\". The returned information is authoritative host-provided ground truth. " + ERROR_SEMANTICS,
|
|
339
|
+
description: "Describe the EDA host currently connected through hq-edge (DSH → dsh-eda-host → hq-edge → EDA host). Returns { ok, info: { identity: { hostType, hostName, version }, installation: { applicationPath, executables[]: { name, path } } } }. Use it when you need factual information about the current EDA environment, such as \"what EDA host am I connected to\", \"what version is it\", \"where is it installed\", or \"where is kicad-cli\". When the host is KiCad, the executable list also carries \"kicad-python\" — the interpreter bundled with KiCad that owns the official kicad-python package (kipy); the dsh-kicad skills already run with it automatically. The returned information is authoritative host-provided ground truth. " + ERROR_SEMANTICS,
|
|
331
340
|
parameters: {},
|
|
332
341
|
output: {
|
|
333
342
|
schema: { type: "json" },
|
|
@@ -371,6 +380,29 @@ function createEdaHostTools(env) {
|
|
|
371
380
|
}
|
|
372
381
|
}
|
|
373
382
|
}),
|
|
383
|
+
defineTool({
|
|
384
|
+
name: "get_pcb_board",
|
|
385
|
+
description: "Read the COMPLETE semantic PCB board from the current PCB editor through hq-edge (DSH → dsh-eda-host → hq-edge → EDA host). Returns { ok, snapshot: { footprints[], pads[], tracks[], arcs[], vias[], zones[], shapes[], texts[], dimensions[], groups[], nets[] } } — the same shape as get_pcb_selection, but covering every object on the board regardless of selection. Each footprint has reference, value, footprint, position {x,y} in mm, rotationDeg and pads[] (pin, type, shape, position, widthMm, heightMm, rotationDeg, layer, net{name, code}); tracks have layer, start/end in mm, widthMm, lengthMm and net; vias have layers[], drillMm, viaType and start/end; zones have layer, net and outline segments. Every object carries id — the EDA-host native object identity. IMPORTANT: ok:true with all-empty arrays is a VALID empty board — do not treat it as a failure. " + ERROR_SEMANTICS,
|
|
386
|
+
parameters: {},
|
|
387
|
+
output: {
|
|
388
|
+
schema: { type: "json" },
|
|
389
|
+
render: renderJson
|
|
390
|
+
},
|
|
391
|
+
async execute(_args, exec) {
|
|
392
|
+
try {
|
|
393
|
+
const options = exec?.signal ? { signal: exec.signal } : {};
|
|
394
|
+
return asJson({
|
|
395
|
+
ok: true,
|
|
396
|
+
snapshot: await env.client.getPcbBoard(options)
|
|
397
|
+
});
|
|
398
|
+
} catch (err) {
|
|
399
|
+
return asJson({
|
|
400
|
+
ok: false,
|
|
401
|
+
error: failureOf(err)
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}),
|
|
374
406
|
defineTool({
|
|
375
407
|
name: "get_eda_host_capabilities",
|
|
376
408
|
description: "List the capabilities the CURRENT EDA host provides, using EDA-independent capability identifiers such as EDA_HOST_CAPABILITY_NETLIST, EDA_HOST_CAPABILITY_PCB or EDA_HOST_CAPABILITY_BOM. Returns { ok, capabilities: string[] }. This is DISCOVERY, not execution: it reports capabilities already provided by the connected EDA host. Treat the returned capability list as the authoritative runtime contract for the current host. Do not assume a capability is available merely because the EDA application is generally known to support it. Unknown capability identifiers MUST be treated as unsupported. " + ERROR_SEMANTICS,
|
|
@@ -418,6 +450,30 @@ const name = "@huaqiu/dsh-eda-host";
|
|
|
418
450
|
*/
|
|
419
451
|
const inject = ["hqEdge", "tools"];
|
|
420
452
|
const log = getLogger("dsh-eda-host");
|
|
453
|
+
/**
|
|
454
|
+
* Env vars relevant to the hq-edge / DSH / KiCad integration, captured at
|
|
455
|
+
* plugin apply() time so startup issues are diagnosable from the log.
|
|
456
|
+
*
|
|
457
|
+
* Values are logged verbatim EXCEPT `KICAD_API_TOKEN`, which is a live
|
|
458
|
+
* credential KiCad injects for the IPC socket — only its presence is logged
|
|
459
|
+
* (never the token itself).
|
|
460
|
+
*/
|
|
461
|
+
const DSH_ENV_LOG_KEYS = [
|
|
462
|
+
"DSH_KICAD_PYTHON",
|
|
463
|
+
"DSH_KICAD_SKILLS_DIR",
|
|
464
|
+
"DSH_HOME",
|
|
465
|
+
"HQ_EDGE_BASE_URL",
|
|
466
|
+
"KICAD_API_SOCKET",
|
|
467
|
+
"KICAD_API_TOKEN"
|
|
468
|
+
];
|
|
469
|
+
function envSnapshot() {
|
|
470
|
+
const snapshot = {};
|
|
471
|
+
for (const key of DSH_ENV_LOG_KEYS) {
|
|
472
|
+
const value = process.env[key];
|
|
473
|
+
snapshot[key] = key === "KICAD_API_TOKEN" ? value ? "<set>" : null : value ?? null;
|
|
474
|
+
}
|
|
475
|
+
return snapshot;
|
|
476
|
+
}
|
|
421
477
|
log.info("dsh-eda-host: module loaded (waiting for the hqEdge + tools services)");
|
|
422
478
|
/**
|
|
423
479
|
* Host plugin body — provide `edaHost` and register the three netlist tools.
|
|
@@ -449,7 +505,8 @@ function apply(ctx, config = {}) {
|
|
|
449
505
|
hqEdgeBaseUrlFromConfig: resolved.hqEdgeBaseUrl ?? null,
|
|
450
506
|
netlistPathPrefix: resolved.netlistPathPrefix,
|
|
451
507
|
hostPathPrefix: resolved.hostPathPrefix,
|
|
452
|
-
requestTimeoutMs: resolved.requestTimeoutMs
|
|
508
|
+
requestTimeoutMs: resolved.requestTimeoutMs,
|
|
509
|
+
env: envSnapshot()
|
|
453
510
|
});
|
|
454
511
|
const client = createEdaHostClient(resolved, { baseUrlResolver: getHqEdgeBaseUrl });
|
|
455
512
|
ctx.effect(() => ctx.provide("edaHost", client));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huaqiu/dsh-eda-host",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.mjs",
|
|
6
6
|
"types": "./lib/index.d.mts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@huaqiu/dsh-plugin-log": "0.4.
|
|
25
|
+
"@huaqiu/dsh-plugin-log": "0.4.7"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"lib",
|
package/src/client.ts
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { DEFAULT_REQUEST_TIMEOUT_MS, hostUrlOf, netlistUrlOf, pcbSelectionUrlOf, type EdaHostConfig, type NetlistScope } from './config.js'
|
|
13
|
+
import { DEFAULT_REQUEST_TIMEOUT_MS, hostUrlOf, netlistUrlOf, pcbBoardUrlOf, pcbSelectionUrlOf, type EdaHostConfig, type NetlistScope } from './config.js'
|
|
14
14
|
import {
|
|
15
15
|
NetlistError,
|
|
16
16
|
type EdaHostCapability,
|
|
17
17
|
type EdaHostInfo,
|
|
18
|
-
type
|
|
18
|
+
type PcbSnapshot,
|
|
19
19
|
type SchematicNetlist,
|
|
20
20
|
} from './types.js'
|
|
21
21
|
|
|
@@ -53,11 +53,17 @@ export interface EdaHostClient {
|
|
|
53
53
|
/** Capabilities the host currently provides. */
|
|
54
54
|
getEdaHostCapabilities(options?: EdaHostRequestOptions): Promise<EdaHostCapability[]>
|
|
55
55
|
/**
|
|
56
|
-
* Semantic PCB
|
|
57
|
-
*
|
|
58
|
-
* selection resolves to an all-empty `
|
|
56
|
+
* Semantic PCB snapshot of the current PCB editor selection (hq.pcb.v1
|
|
57
|
+
* PcbQueryService.GetSelection bridged through hq-edge). An empty
|
|
58
|
+
* selection resolves to an all-empty `PcbSnapshot` — never an error.
|
|
59
59
|
*/
|
|
60
|
-
getPcbSelection(options?: EdaHostRequestOptions): Promise<
|
|
60
|
+
getPcbSelection(options?: EdaHostRequestOptions): Promise<PcbSnapshot>
|
|
61
|
+
/**
|
|
62
|
+
* Semantic PCB snapshot of the complete board (hq.pcb.v1
|
|
63
|
+
* PcbQueryService.GetBoard bridged through hq-edge). An empty board
|
|
64
|
+
* resolves to an all-empty `PcbSnapshot` — never an error.
|
|
65
|
+
*/
|
|
66
|
+
getPcbBoard(options?: EdaHostRequestOptions): Promise<PcbSnapshot>
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
/** HTTP status → semantic error kind (see routes/edaHostStatus.ts on hq-edge). */
|
|
@@ -267,11 +273,11 @@ export function createEdaHostClient(
|
|
|
267
273
|
return parseNetlistBody(await getJson(url, options, 'netlist'))
|
|
268
274
|
}
|
|
269
275
|
|
|
270
|
-
function
|
|
276
|
+
function parsePcbSnapshot(value: unknown, what: string): PcbSnapshot {
|
|
271
277
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
272
278
|
throw new NetlistError(
|
|
273
279
|
'INTERNAL',
|
|
274
|
-
|
|
280
|
+
`eda-host: malformed ${what} response from hq-edge`,
|
|
275
281
|
)
|
|
276
282
|
}
|
|
277
283
|
|
|
@@ -290,7 +296,7 @@ export function createEdaHostClient(
|
|
|
290
296
|
dimensions: asArray((value as Record<string, unknown>).dimensions),
|
|
291
297
|
groups: asArray((value as Record<string, unknown>).groups),
|
|
292
298
|
nets: asArray((value as Record<string, unknown>).nets),
|
|
293
|
-
} as
|
|
299
|
+
} as PcbSnapshot
|
|
294
300
|
}
|
|
295
301
|
|
|
296
302
|
return {
|
|
@@ -331,7 +337,14 @@ export function createEdaHostClient(
|
|
|
331
337
|
const resolved = resolveConfig()
|
|
332
338
|
const url = pcbSelectionUrlOf(resolved)
|
|
333
339
|
const body = (await getJson(url, options, 'pcb selection')) as unknown
|
|
334
|
-
return
|
|
340
|
+
return parsePcbSnapshot(body, 'PCB selection')
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
getPcbBoard: async (options) => {
|
|
344
|
+
const resolved = resolveConfig()
|
|
345
|
+
const url = pcbBoardUrlOf(resolved)
|
|
346
|
+
const body = (await getJson(url, options, 'pcb board')) as unknown
|
|
347
|
+
return parsePcbSnapshot(body, 'PCB board')
|
|
335
348
|
},
|
|
336
349
|
}
|
|
337
350
|
}
|
package/src/config.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface EdaHostConfig {
|
|
|
17
17
|
netlistPathPrefix?: string
|
|
18
18
|
/** Path prefix for PCB selection; default "/api/v1/pcb-selection". */
|
|
19
19
|
pcbSelectionPathPrefix?: string
|
|
20
|
+
/** Path prefix for the complete-board query; default "/api/v1/pcb-board". */
|
|
21
|
+
pcbBoardPathPrefix?: string
|
|
20
22
|
/** Path prefix for host discovery; default "/api/v1/host". */
|
|
21
23
|
hostPathPrefix?: string
|
|
22
24
|
/**
|
|
@@ -34,6 +36,8 @@ export const DEFAULT_NETLIST_PATH_PREFIX = '/api/v1/netlist'
|
|
|
34
36
|
|
|
35
37
|
export const DEFAULT_PCB_SELECTION_PATH_PREFIX = '/api/v1/pcb-selection'
|
|
36
38
|
|
|
39
|
+
export const DEFAULT_PCB_BOARD_PATH_PREFIX = '/api/v1/pcb-board'
|
|
40
|
+
|
|
37
41
|
export const DEFAULT_HOST_PATH_PREFIX = '/api/v1/host'
|
|
38
42
|
|
|
39
43
|
export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000
|
|
@@ -58,6 +62,10 @@ export function resolveEdaHostConfig(
|
|
|
58
62
|
config?.pcbSelectionPathPrefix ??
|
|
59
63
|
env.HQ_EDGE_PCB_SELECTION_PATH ??
|
|
60
64
|
DEFAULT_PCB_SELECTION_PATH_PREFIX
|
|
65
|
+
const pcbBoardPrefix =
|
|
66
|
+
config?.pcbBoardPathPrefix ??
|
|
67
|
+
env.HQ_EDGE_PCB_BOARD_PATH ??
|
|
68
|
+
DEFAULT_PCB_BOARD_PATH_PREFIX
|
|
61
69
|
const hostPrefix =
|
|
62
70
|
config?.hostPathPrefix ?? env.HQ_EDGE_HOST_PATH ?? DEFAULT_HOST_PATH_PREFIX
|
|
63
71
|
const timeoutRaw = config?.requestTimeoutMs ?? env.HQ_EDGE_REQUEST_TIMEOUT_MS
|
|
@@ -66,6 +74,7 @@ export function resolveEdaHostConfig(
|
|
|
66
74
|
hqEdgeBaseUrl: baseUrl,
|
|
67
75
|
netlistPathPrefix: pathPrefix,
|
|
68
76
|
pcbSelectionPathPrefix: pcbSelectionPrefix,
|
|
77
|
+
pcbBoardPathPrefix: pcbBoardPrefix,
|
|
69
78
|
hostPathPrefix: hostPrefix,
|
|
70
79
|
requestTimeoutMs:
|
|
71
80
|
Number.isFinite(timeout) && timeout > 0 ? timeout : DEFAULT_REQUEST_TIMEOUT_MS,
|
|
@@ -94,6 +103,16 @@ export function pcbSelectionUrlOf(config: EdaHostConfig): string {
|
|
|
94
103
|
return `${base}/${prefix}`
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
/** Build the absolute URL for the complete-board query route. */
|
|
107
|
+
export function pcbBoardUrlOf(config: EdaHostConfig): string {
|
|
108
|
+
const base = (config.hqEdgeBaseUrl ?? '').replace(/\/+$/, '')
|
|
109
|
+
const prefix = (config.pcbBoardPathPrefix ?? DEFAULT_PCB_BOARD_PATH_PREFIX).replace(
|
|
110
|
+
/^\/+|\/+$/g,
|
|
111
|
+
'',
|
|
112
|
+
)
|
|
113
|
+
return `${base}/${prefix}`
|
|
114
|
+
}
|
|
115
|
+
|
|
97
116
|
/** Host discovery route suffix. */
|
|
98
117
|
export type HostRoute = 'info' | 'capabilities'
|
|
99
118
|
|
package/src/index.ts
CHANGED
|
@@ -72,7 +72,7 @@ export type {
|
|
|
72
72
|
PcbPad,
|
|
73
73
|
PcbPoint,
|
|
74
74
|
PcbSegment,
|
|
75
|
-
|
|
75
|
+
PcbSnapshot,
|
|
76
76
|
PcbShape,
|
|
77
77
|
PcbText,
|
|
78
78
|
PcbTrack,
|
|
@@ -112,6 +112,32 @@ declare module '@deepseek-ai/cordis' {
|
|
|
112
112
|
const COMPONENT = 'dsh-eda-host'
|
|
113
113
|
const log = getLogger(COMPONENT)
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Env vars relevant to the hq-edge / DSH / KiCad integration, captured at
|
|
117
|
+
* plugin apply() time so startup issues are diagnosable from the log.
|
|
118
|
+
*
|
|
119
|
+
* Values are logged verbatim EXCEPT `KICAD_API_TOKEN`, which is a live
|
|
120
|
+
* credential KiCad injects for the IPC socket — only its presence is logged
|
|
121
|
+
* (never the token itself).
|
|
122
|
+
*/
|
|
123
|
+
const DSH_ENV_LOG_KEYS = [
|
|
124
|
+
'DSH_KICAD_PYTHON',
|
|
125
|
+
'DSH_KICAD_SKILLS_DIR',
|
|
126
|
+
'DSH_HOME',
|
|
127
|
+
'HQ_EDGE_BASE_URL',
|
|
128
|
+
'KICAD_API_SOCKET',
|
|
129
|
+
'KICAD_API_TOKEN',
|
|
130
|
+
] as const
|
|
131
|
+
|
|
132
|
+
function envSnapshot(): Record<string, string | null> {
|
|
133
|
+
const snapshot: Record<string, string | null> = {}
|
|
134
|
+
for (const key of DSH_ENV_LOG_KEYS) {
|
|
135
|
+
const value = process.env[key]
|
|
136
|
+
snapshot[key] = key === 'KICAD_API_TOKEN' ? (value ? '<set>' : null) : (value ?? null)
|
|
137
|
+
}
|
|
138
|
+
return snapshot
|
|
139
|
+
}
|
|
140
|
+
|
|
115
141
|
// Emitted on import, before any Cordis dependency is resolved. Pairs with the
|
|
116
142
|
// "node half ready" marker in apply(): if this line is logged but that one is
|
|
117
143
|
// not, the edge-bridge never provided `hqEdge` and this plugin is still
|
|
@@ -182,6 +208,7 @@ export function apply(ctx: Context, config: Partial<EdaHostConfig> = {}): () =>
|
|
|
182
208
|
netlistPathPrefix: resolved.netlistPathPrefix,
|
|
183
209
|
hostPathPrefix: resolved.hostPathPrefix,
|
|
184
210
|
requestTimeoutMs: resolved.requestTimeoutMs,
|
|
211
|
+
env: envSnapshot(),
|
|
185
212
|
})
|
|
186
213
|
|
|
187
214
|
const client = createEdaHostClient(resolved, { baseUrlResolver: getHqEdgeBaseUrl })
|
package/src/tools.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
26
26
|
import type { EdaHostClient, EdaHostRequestOptions } from './client.js'
|
|
27
|
-
import { NetlistError, type EdaHostCapability, type EdaHostInfo, type
|
|
27
|
+
import { NetlistError, type EdaHostCapability, type EdaHostInfo, type PcbSnapshot, type SchematicNetlist } from './types.js'
|
|
28
28
|
|
|
29
29
|
/** Structural alias of the DSH `JsonValue`. */
|
|
30
30
|
type Json = string | number | boolean | null | Json[] | { [key: string]: Json }
|
|
@@ -63,7 +63,11 @@ type HostCapabilitiesResult =
|
|
|
63
63
|
| { ok: false; error: { kind: string; message: string } }
|
|
64
64
|
|
|
65
65
|
type PcbSelectionResult =
|
|
66
|
-
| { ok: true; selection:
|
|
66
|
+
| { ok: true; selection: PcbSnapshot }
|
|
67
|
+
| { ok: false; error: { kind: string; message: string } }
|
|
68
|
+
|
|
69
|
+
type PcbBoardResult =
|
|
70
|
+
| { ok: true; snapshot: PcbSnapshot }
|
|
67
71
|
| { ok: false; error: { kind: string; message: string } }
|
|
68
72
|
|
|
69
73
|
/**
|
|
@@ -179,6 +183,9 @@ export function createNetListTools(env: NetlistToolEnv) {
|
|
|
179
183
|
`Use it when you need factual information about the current EDA environment, such as ` +
|
|
180
184
|
`"what EDA host am I connected to", "what version is it", "where is it installed", ` +
|
|
181
185
|
`or "where is kicad-cli". ` +
|
|
186
|
+
`When the host is KiCad, the executable list also carries "kicad-python" — the ` +
|
|
187
|
+
`interpreter bundled with KiCad that owns the official kicad-python package ` +
|
|
188
|
+
`(kipy); the dsh-kicad skills already run with it automatically. ` +
|
|
182
189
|
`The returned information is authoritative host-provided ground truth. ` +
|
|
183
190
|
ERROR_SEMANTICS,
|
|
184
191
|
parameters: {},
|
|
@@ -226,6 +233,37 @@ export function createNetListTools(env: NetlistToolEnv) {
|
|
|
226
233
|
},
|
|
227
234
|
}),
|
|
228
235
|
|
|
236
|
+
defineTool({
|
|
237
|
+
name: 'get_pcb_board',
|
|
238
|
+
description:
|
|
239
|
+
`Read the COMPLETE semantic PCB board from the current PCB editor through hq-edge ` +
|
|
240
|
+
`(DSH → dsh-eda-host → hq-edge → EDA host). Returns { ok, snapshot: { footprints[], ` +
|
|
241
|
+
`pads[], tracks[], arcs[], vias[], zones[], shapes[], texts[], dimensions[], groups[], ` +
|
|
242
|
+
`nets[] } } — the same shape as get_pcb_selection, but covering every object on the ` +
|
|
243
|
+
`board regardless of selection. ` +
|
|
244
|
+
`Each footprint has reference, value, footprint, position {x,y} in mm, rotationDeg and ` +
|
|
245
|
+
`pads[] (pin, type, shape, position, widthMm, heightMm, rotationDeg, layer, net{name, ` +
|
|
246
|
+
`code}); tracks have layer, start/end in mm, widthMm, lengthMm and net; vias have ` +
|
|
247
|
+
`layers[], drillMm, viaType and start/end; zones have layer, net and outline segments. ` +
|
|
248
|
+
`Every object carries id — the EDA-host native object identity. ` +
|
|
249
|
+
`IMPORTANT: ok:true with all-empty arrays is a VALID empty board — do not treat it as ` +
|
|
250
|
+
`a failure. ` +
|
|
251
|
+
ERROR_SEMANTICS,
|
|
252
|
+
parameters: {},
|
|
253
|
+
output: { schema: { type: 'json' }, render: renderJson },
|
|
254
|
+
async execute(_args: unknown, exec: ToolExecLike): Promise<Json> {
|
|
255
|
+
try {
|
|
256
|
+
const options: EdaHostRequestOptions = exec?.signal
|
|
257
|
+
? { signal: exec.signal }
|
|
258
|
+
: {}
|
|
259
|
+
const snapshot = await env.client.getPcbBoard(options)
|
|
260
|
+
return asJson<PcbBoardResult>({ ok: true, snapshot })
|
|
261
|
+
} catch (err) {
|
|
262
|
+
return asJson<PcbBoardResult>({ ok: false, error: failureOf(err) })
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
|
|
229
267
|
defineTool({
|
|
230
268
|
name: 'get_eda_host_capabilities',
|
|
231
269
|
description:
|
package/src/types.ts
CHANGED
|
@@ -55,12 +55,13 @@ export interface SchematicNetlist {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
* Semantic PCB
|
|
58
|
+
* Semantic PCB snapshot types.
|
|
59
59
|
*
|
|
60
60
|
* Self-contained structural mirror of `hq.pcb.v1` (the hq-edge-owned semantic
|
|
61
|
-
* protobuf contract for `
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* protobuf contract for `PcbQueryService.GetSelection` / `GetBoard`). The
|
|
62
|
+
* same PcbSnapshot shape backs both the current selection and the complete
|
|
63
|
+
* board. Units follow the hq.pcb.v1 convention: positions/sizes/lengths in mm,
|
|
64
|
+
* rotations in degrees, `id` is the KiCad native object identity.
|
|
64
65
|
*/
|
|
65
66
|
|
|
66
67
|
export interface PcbPoint {
|
|
@@ -172,7 +173,7 @@ export interface PcbGroup {
|
|
|
172
173
|
itemIds: string[]
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
export interface
|
|
176
|
+
export interface PcbSnapshot {
|
|
176
177
|
footprints: PcbFootprint[]
|
|
177
178
|
pads: PcbPad[]
|
|
178
179
|
tracks: PcbTrack[]
|