llm-switcher 1.1.10 → 1.2.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/CHANGELOG.md +51 -0
- package/README.md +202 -257
- package/README.vi.md +200 -256
- package/blindfold/blindfold.mjs +200 -53
- package/blindfold/make-certs.sh +26 -7
- package/catalog.mjs +246 -0
- package/config.example.json +12 -34
- package/docs/TOKEN-OPTIMIZER-INTEROP.md +110 -110
- package/docs/codex-blindfold.md +28 -17
- package/docs/cross-platform.md +16 -7
- package/docs/diagrams/ir-healer-pipeline.mmd +16 -0
- package/docs/diagrams/ir-healer-pipeline.png +0 -0
- package/docs/diagrams/ir-healer-pipeline.svg +90 -0
- package/docs/diagrams/ir-translation-pipeline.html +14925 -0
- package/docs/diagrams/ir-translation-pipeline.sequence.json +31 -0
- package/docs/diagrams/ir-translation-pipeline.svg +5128 -0
- package/docs/diagrams/system-architecture.architecture.json +76 -0
- package/docs/diagrams/system-architecture.html +14978 -0
- package/docs/diagrams/system-architecture.svg +5147 -0
- package/docs/diagrams/system-topology.mmd +30 -0
- package/docs/diagrams/system-topology.png +0 -0
- package/docs/diagrams/system-topology.svg +125 -0
- package/docs/response-matrix.json +1130 -1130
- package/ensure-ca-bundle.mjs +28 -0
- package/formats.mjs +13 -155
- package/mcp.mjs +39 -11
- package/package.json +1 -1
- package/proxy.mjs +92 -27
- package/shim.mjs +200 -57
- package/skills/llm-switcher/SKILL.md +93 -88
- package/state.mjs +1100 -191
- package/switch +0 -0
- package/switch.cmd +2 -2
- package/switch.mjs +228 -53
- package/tests/blindfold-e2e.test.mjs +380 -0
- package/tests/blindfold-task5.test.mjs +429 -0
- package/tests/blindfold.task3.test.mjs +700 -0
- package/tests/blindfold.test.mjs +10 -5
- package/tests/catalog.test.mjs +147 -0
- package/tests/contract-lab.test.mjs +22 -7
- package/tests/formats.test.mjs +33 -46
- package/tests/gateway.e2e.test.mjs +136 -36
- package/tests/helpers.mjs +24 -24
- package/tests/lifecycle.test.mjs +16 -10
- package/tests/live-optimizer-interop.mjs +205 -205
- package/tests/mcp.test.mjs +78 -2
- package/tests/real-user-sim.test.mjs +464 -0
- package/tests/shim.test.mjs +159 -66
- package/tests/state.test.mjs +975 -193
- package/tests/switch.test.mjs +446 -2
- package/ui.html +61 -154
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
flowchart LR
|
|
2
|
+
subgraph Clients[" Dev Clients & Coding CLIs "]
|
|
3
|
+
CC["Claude Code CLI\n(/v1/messages)"]
|
|
4
|
+
CDX["OpenAI Codex CLI\n(/v1/responses)"]
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
subgraph Optimizers[" Optional Middle-Layer "]
|
|
8
|
+
OPT["Token Optimizers\n(Headroom / RTK)"]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
subgraph Gateway[" LLM Switcher Edge Gateway (:3456) "]
|
|
12
|
+
ROUTER["Edge Router\n(Zero-Mutation)"]
|
|
13
|
+
HEALER["Healer Engine\n(Auto-Fix Schemas)"]
|
|
14
|
+
IR["Bi-Directional IR\n(Event Stream Synth)"]
|
|
15
|
+
ROUTER --> HEALER --> IR
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
subgraph Upstreams[" Upstream Providers "]
|
|
19
|
+
INTACT["intact Gateway\n(Recommended Pooler)"]
|
|
20
|
+
OTHER["9Router / Vertex / Other"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
CC -->|direct /v1/messages| ROUTER
|
|
24
|
+
CDX -->|direct /v1/responses| ROUTER
|
|
25
|
+
CC -.->|prune prompt| OPT
|
|
26
|
+
CDX -.->|prune prompt| OPT
|
|
27
|
+
OPT -->|forward :3456| ROUTER
|
|
28
|
+
|
|
29
|
+
IR -->|contract & pool| INTACT
|
|
30
|
+
IR -->|standard call| OTHER
|
|
Binary file
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1712.757 298.51666666666665" width="1712.757" height="298.51666666666665" style="--bg:#1a1b26;--fg:#a9b1d6;--line:#3d59a1;--accent:#7aa2f7;--muted:#565f89;background:var(--bg)">
|
|
2
|
+
<style>
|
|
3
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
4
|
+
text { font-family: 'Inter', system-ui, sans-serif; }
|
|
5
|
+
svg {
|
|
6
|
+
/* Derived from --bg and --fg (overridable via --line, --accent, etc.) */
|
|
7
|
+
--_text: var(--fg);
|
|
8
|
+
--_text-sec: var(--muted, color-mix(in srgb, var(--fg) 60%, var(--bg)));
|
|
9
|
+
--_text-muted: var(--muted, color-mix(in srgb, var(--fg) 40%, var(--bg)));
|
|
10
|
+
--_text-faint: color-mix(in srgb, var(--fg) 25%, var(--bg));
|
|
11
|
+
--_line: var(--line, color-mix(in srgb, var(--fg) 50%, var(--bg)));
|
|
12
|
+
--_arrow: var(--accent, color-mix(in srgb, var(--fg) 85%, var(--bg)));
|
|
13
|
+
--_node-fill: var(--surface, color-mix(in srgb, var(--fg) 3%, var(--bg)));
|
|
14
|
+
--_node-stroke: var(--border, color-mix(in srgb, var(--fg) 20%, var(--bg)));
|
|
15
|
+
--_group-fill: var(--bg);
|
|
16
|
+
--_group-hdr: color-mix(in srgb, var(--fg) 5%, var(--bg));
|
|
17
|
+
--_inner-stroke: color-mix(in srgb, var(--fg) 12%, var(--bg));
|
|
18
|
+
--_key-badge: color-mix(in srgb, var(--fg) 10%, var(--bg));
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
<defs>
|
|
22
|
+
<marker id="arrowhead" markerWidth="8" markerHeight="5" refX="7" refY="2.5" orient="auto">
|
|
23
|
+
<polygon points="0 0, 8 2.5, 0 5" fill="var(--_arrow)" stroke="var(--_arrow)" stroke-width="0.75" stroke-linejoin="round" />
|
|
24
|
+
</marker>
|
|
25
|
+
<marker id="arrowhead-start" markerWidth="8" markerHeight="5" refX="1" refY="2.5" orient="auto-start-reverse">
|
|
26
|
+
<polygon points="8 0, 0 2.5, 8 5" fill="var(--_arrow)" stroke="var(--_arrow)" stroke-width="0.75" stroke-linejoin="round" />
|
|
27
|
+
</marker>
|
|
28
|
+
</defs>
|
|
29
|
+
<g class="subgraph" data-id="Clients" data-label=" Dev Clients & Coding CLIs ">
|
|
30
|
+
<rect x="40" y="40" width="180.654" height="191.60000000000002" rx="0" ry="0" fill="var(--_group-fill)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
31
|
+
<rect x="40" y="40" width="180.654" height="28" rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
32
|
+
<text x="52" y="54" font-size="12" font-weight="600" fill="var(--_text-sec)" dy="4.199999999999999"> Dev Clients & Coding CLIs </text>
|
|
33
|
+
</g>
|
|
34
|
+
<g class="subgraph" data-id="Optimizers" data-label=" Optional Middle-Layer ">
|
|
35
|
+
<rect x="384.674" y="90.56666666666666" width="176.949" height="113.80000000000001" rx="0" ry="0" fill="var(--_group-fill)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
36
|
+
<rect x="384.674" y="90.56666666666666" width="176.949" height="28" rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
37
|
+
<text x="396.674" y="104.56666666666666" font-size="12" font-weight="600" fill="var(--_text-sec)" dy="4.199999999999999"> Optional Middle-Layer </text>
|
|
38
|
+
</g>
|
|
39
|
+
<g class="subgraph" data-id="Gateway" data-label=" LLM Switcher Edge Gateway (:3456) ">
|
|
40
|
+
<rect x="728.0190000000001" y="92.55000000000001" width="563.89" height="113.80000000000001" rx="0" ry="0" fill="var(--_group-fill)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
41
|
+
<rect x="728.0190000000001" y="92.55000000000001" width="563.89" height="28" rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
42
|
+
<text x="740.0190000000001" y="106.55000000000001" font-size="12" font-weight="600" fill="var(--_text-sec)" dy="4.199999999999999"> LLM Switcher Edge Gateway (:3456) </text>
|
|
43
|
+
</g>
|
|
44
|
+
<g class="subgraph" data-id="Upstreams" data-label=" Upstream Providers ">
|
|
45
|
+
<rect x="1462.4630000000002" y="48.45" width="210.29399999999998" height="174.70000000000002" rx="0" ry="0" fill="var(--_group-fill)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
46
|
+
<rect x="1462.4630000000002" y="48.45" width="210.29399999999998" height="28" rx="0" ry="0" fill="var(--_group-hdr)" stroke="var(--_node-stroke)" stroke-width="1" />
|
|
47
|
+
<text x="1474.4630000000002" y="62.45" font-size="12" font-weight="600" fill="var(--_text-sec)" dy="4.199999999999999"> Upstream Providers </text>
|
|
48
|
+
</g>
|
|
49
|
+
<polyline class="edge" data-from="CC" data-to="ROUTER" data-style="solid" data-arrow-start="false" data-arrow-end="true" data-label="direct /v1/messages" points="200.208,101.93333333333334 366.674,101.93333333333334 366.674,56.26666666666667 710.0190000000001,56.26666666666667 710.0190000000001,150 744.0190000000001,150" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
50
|
+
<polyline class="edge" data-from="CDX" data-to="ROUTER" data-style="solid" data-arrow-start="false" data-arrow-end="true" data-label="direct /v1/responses" points="209.1,197.66666666666669 366.674,197.66666666666669 366.674,239.36666666666667 710.0190000000001,239.36666666666667 710.0190000000001,176.9 744.0190000000001,176.9" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
51
|
+
<polyline class="edge" data-from="CC" data-to="OPT" data-style="dotted" data-arrow-start="false" data-arrow-end="true" data-label="prune prompt" points="200.208,119.86666666666667 254.654,119.86666666666667 254.654,144.81666666666666 366.674,144.81666666666666 366.674,152.5 400.674,152.5" fill="none" stroke="var(--_line)" stroke-width="1" stroke-dasharray="4 4" marker-end="url(#arrowhead)" />
|
|
52
|
+
<polyline class="edge" data-from="CDX" data-to="OPT" data-style="dotted" data-arrow-start="false" data-arrow-end="true" data-label="prune prompt" points="209.1,179.73333333333335 254.654,179.73333333333335 254.654,178.11666666666667 366.674,178.11666666666667 366.674,170.43333333333334 400.674,170.43333333333334" fill="none" stroke="var(--_line)" stroke-width="1" stroke-dasharray="4 4" marker-end="url(#arrowhead)" />
|
|
53
|
+
<polyline class="edge" data-from="OPT" data-to="ROUTER" data-style="solid" data-arrow-start="false" data-arrow-end="true" data-label="forward :3456" points="545.623,161.46666666666667 710.019,161.4666666666667 710.019,163.45000000000002 744.0190000000001,163.45000000000002" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
54
|
+
<polyline class="edge" data-from="IR" data-to="INTACT" data-style="solid" data-arrow-start="false" data-arrow-end="true" data-label="contract & pool" points="1275.909,172.41666666666669 1325.909,172.41666666666669 1325.909,180.10000000000002 1444.4630000000002,180.10000000000002 1444.4630000000002,180.25 1478.4630000000002,180.25000000000003" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
55
|
+
<polyline class="edge" data-from="IR" data-to="OTHER" data-style="solid" data-arrow-start="false" data-arrow-end="true" data-label="standard call" points="1275.909,154.48333333333335 1333.631,154.48333333333335 1333.631,128.925 1444.4630000000002,128.925 1444.4630000000002,110.9 1478.4630000000002,110.90000000000002" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
56
|
+
<polyline class="edge" data-from="ROUTER" data-to="HEALER" data-style="solid" data-arrow-start="false" data-arrow-end="true" points="876.3710000000001,163.45000000000002 916.3710000000001,163.45000000000002" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
57
|
+
<polyline class="edge" data-from="HEALER" data-to="IR" data-style="solid" data-arrow-start="false" data-arrow-end="true" points="1072.4350000000002,163.45000000000002 1112.4350000000002,163.45000000000002" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
58
|
+
<g class="edge-label" data-from="CC" data-to="ROUTER" data-label="direct /v1/messages">
|
|
59
|
+
<rect x="592.2990000000001" y="40.26666666666667" width="109.72000000000001" height="30.3" rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1" />
|
|
60
|
+
<text x="647.1590000000001" y="55.41666666666667" text-anchor="middle" font-size="11" font-weight="400" fill="var(--_text-sec)" dy="3.8499999999999996">direct /v1/messages</text>
|
|
61
|
+
</g>
|
|
62
|
+
<g class="edge-label" data-from="CDX" data-to="ROUTER" data-label="direct /v1/responses">
|
|
63
|
+
<rect x="588.7350000000001" y="223.36666666666667" width="113.28400000000002" height="30.3" rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1" />
|
|
64
|
+
<text x="645.3770000000002" y="238.51666666666668" text-anchor="middle" font-size="11" font-weight="400" fill="var(--_text-sec)" dy="3.8499999999999996">direct /v1/responses</text>
|
|
65
|
+
</g>
|
|
66
|
+
<g class="edge-label" data-from="CC" data-to="OPT" data-label="prune prompt">
|
|
67
|
+
<rect x="262.654" y="128.81666666666666" width="80.02000000000001" height="30.3" rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1" />
|
|
68
|
+
<text x="302.664" y="143.96666666666667" text-anchor="middle" font-size="11" font-weight="400" fill="var(--_text-sec)" dy="3.8499999999999996">prune prompt</text>
|
|
69
|
+
</g>
|
|
70
|
+
<g class="edge-label" data-from="CDX" data-to="OPT" data-label="prune prompt">
|
|
71
|
+
<rect x="262.654" y="162.11666666666667" width="80.02000000000001" height="30.3" rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1" />
|
|
72
|
+
<text x="302.664" y="177.26666666666668" text-anchor="middle" font-size="11" font-weight="400" fill="var(--_text-sec)" dy="3.8499999999999996">prune prompt</text>
|
|
73
|
+
</g>
|
|
74
|
+
<g class="edge-label" data-from="OPT" data-to="ROUTER" data-label="forward :3456">
|
|
75
|
+
<rect x="603.623" y="145.46666666666667" width="82.39600000000002" height="30.3" rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1" />
|
|
76
|
+
<text x="644.821" y="160.61666666666667" text-anchor="middle" font-size="11" font-weight="400" fill="var(--_text-sec)" dy="3.8499999999999996">forward :3456</text>
|
|
77
|
+
</g>
|
|
78
|
+
<g class="edge-label" data-from="IR" data-to="INTACT" data-label="contract & pool">
|
|
79
|
+
<rect x="1333.909" y="164.10000000000002" width="86.55400000000002" height="30.3" rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1" />
|
|
80
|
+
<text x="1377.1860000000001" y="179.25000000000003" text-anchor="middle" font-size="11" font-weight="400" fill="var(--_text-sec)" dy="3.8499999999999996">contract & pool</text>
|
|
81
|
+
</g>
|
|
82
|
+
<g class="edge-label" data-from="IR" data-to="OTHER" data-label="standard call">
|
|
83
|
+
<rect x="1341.631" y="112.92500000000001" width="78.83200000000001" height="30.3" rx="2" ry="2" fill="var(--bg)" stroke="var(--_inner-stroke)" stroke-width="1" />
|
|
84
|
+
<text x="1381.047" y="128.07500000000002" text-anchor="middle" font-size="11" font-weight="400" fill="var(--_text-sec)" dy="3.8499999999999996">standard call</text>
|
|
85
|
+
</g>
|
|
86
|
+
<g class="node" data-id="CC" data-label="Claude Code CLI
|
|
87
|
+
(/v1/messages)" data-shape="rectangle">
|
|
88
|
+
<rect x="60.446" y="84" width="139.762" height="53.800000000000004" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
89
|
+
<text x="130.327" y="110.9" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)"><tspan x="130.327" dy="-3.9000000000000012">Claude Code CLI</tspan><tspan x="130.327" dy="16.900000000000002">(/v1/messages)</tspan></text>
|
|
90
|
+
</g>
|
|
91
|
+
<g class="node" data-id="CDX" data-label="OpenAI Codex CLI
|
|
92
|
+
(/v1/responses)" data-shape="rectangle">
|
|
93
|
+
<rect x="60.446" y="161.8" width="148.654" height="53.800000000000004" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
94
|
+
<text x="134.773" y="188.70000000000002" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)"><tspan x="134.773" dy="-3.9000000000000012">OpenAI Codex CLI</tspan><tspan x="134.773" dy="16.900000000000002">(/v1/responses)</tspan></text>
|
|
95
|
+
</g>
|
|
96
|
+
<g class="node" data-id="OPT" data-label="Token Optimizers
|
|
97
|
+
(Headroom / RTK)" data-shape="rectangle">
|
|
98
|
+
<rect x="400.674" y="134.56666666666666" width="144.949" height="53.800000000000004" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
99
|
+
<text x="473.1485" y="161.46666666666667" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)"><tspan x="473.1485" dy="-3.9000000000000012">Token Optimizers</tspan><tspan x="473.1485" dy="16.900000000000002">(Headroom / RTK)</tspan></text>
|
|
100
|
+
</g>
|
|
101
|
+
<g class="node" data-id="ROUTER" data-label="Edge Router
|
|
102
|
+
(Zero-Mutation)" data-shape="rectangle">
|
|
103
|
+
<rect x="744.0190000000001" y="136.55" width="132.352" height="53.800000000000004" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
104
|
+
<text x="810.1950000000002" y="163.45000000000002" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)"><tspan x="810.1950000000002" dy="-3.9000000000000012">Edge Router</tspan><tspan x="810.1950000000002" dy="16.900000000000002">(Zero-Mutation)</tspan></text>
|
|
105
|
+
</g>
|
|
106
|
+
<g class="node" data-id="HEALER" data-label="Healer Engine
|
|
107
|
+
(Auto-Fix Schemas)" data-shape="rectangle">
|
|
108
|
+
<rect x="916.3710000000001" y="136.55" width="156.064" height="53.800000000000004" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
109
|
+
<text x="994.4030000000001" y="163.45000000000002" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)"><tspan x="994.4030000000001" dy="-3.9000000000000012">Healer Engine</tspan><tspan x="994.4030000000001" dy="16.900000000000002">(Auto-Fix Schemas)</tspan></text>
|
|
110
|
+
</g>
|
|
111
|
+
<g class="node" data-id="IR" data-label="Bi-Directional IR
|
|
112
|
+
(Event Stream Synth)" data-shape="rectangle">
|
|
113
|
+
<rect x="1112.4350000000002" y="136.55" width="163.474" height="53.800000000000004" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
114
|
+
<text x="1194.1720000000003" y="163.45000000000002" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)"><tspan x="1194.1720000000003" dy="-3.9000000000000012">Bi-Directional IR</tspan><tspan x="1194.1720000000003" dy="16.900000000000002">(Event Stream Synth)</tspan></text>
|
|
115
|
+
</g>
|
|
116
|
+
<g class="node" data-id="INTACT" data-label="intact Gateway
|
|
117
|
+
(Recommended Pooler)" data-shape="rectangle">
|
|
118
|
+
<rect x="1478.4630000000002" y="153.35000000000002" width="177.55299999999997" height="53.800000000000004" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
119
|
+
<text x="1567.2395000000001" y="180.25000000000003" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)"><tspan x="1567.2395000000001" dy="-3.9000000000000012">intact Gateway</tspan><tspan x="1567.2395000000001" dy="16.900000000000002">(Recommended Pooler)</tspan></text>
|
|
120
|
+
</g>
|
|
121
|
+
<g class="node" data-id="OTHER" data-label="9Router / Vertex / Other" data-shape="rectangle">
|
|
122
|
+
<rect x="1478.4630000000002" y="92.45" width="178.29399999999998" height="36.900000000000006" rx="0" ry="0" fill="var(--_node-fill)" stroke="var(--_node-stroke)" stroke-width="0.75" />
|
|
123
|
+
<text x="1567.6100000000001" y="110.9" text-anchor="middle" font-size="13" font-weight="500" fill="var(--_text)" dy="4.55">9Router / Vertex / Other</text>
|
|
124
|
+
</g>
|
|
125
|
+
</svg>
|