@qadanm/opencode-smart-router 0.0.0-stage → 0.4.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 +118 -0
- package/CONTRIBUTING.md +96 -0
- package/LICENSE +21 -0
- package/README.md +341 -2
- package/SECURITY.md +174 -0
- package/config.example.json +108 -0
- package/dist/allowance.js +487 -0
- package/dist/gui-experimental.cjs +1 -0
- package/dist/index.js +11111 -0
- package/dist/npm/index.js +40 -0
- package/dist/tui.js +587 -0
- package/docs/ARCHITECTURE.md +104 -0
- package/docs/DEBUGGING-GUI.md +70 -0
- package/docs/INSTALL.md +120 -0
- package/docs/LIMITATIONS.md +213 -0
- package/docs/MODELS.md +124 -0
- package/docs/OPENDESIGN-INTEGRATION.md +635 -0
- package/docs/QA.md +142 -0
- package/docs/SHARED-CORE.md +72 -0
- package/install +75 -0
- package/package.json +92 -4
- package/scripts/install.sh +324 -0
- package/scripts/uninstall.sh +91 -0
- package/src/allowance.ts +135 -0
- package/src/core/classify/classifier.ts +305 -0
- package/src/core/config-writer.ts +167 -0
- package/src/core/config.ts +1030 -0
- package/src/core/context/strategy.ts +188 -0
- package/src/core/context-health.ts +39 -0
- package/src/core/failures.ts +398 -0
- package/src/core/history/opencode.ts +173 -0
- package/src/core/models/protocol.ts +22 -0
- package/src/core/models/registry.ts +203 -0
- package/src/core/models/seed.ts +771 -0
- package/src/core/offline/mode.ts +321 -0
- package/src/core/plan-page.ts +390 -0
- package/src/core/plans/catalog.ts +481 -0
- package/src/core/policy/calibration.ts +259 -0
- package/src/core/policy/eligibility.ts +578 -0
- package/src/core/providers/enablement.ts +143 -0
- package/src/core/providers/opendesign-credits.ts +206 -0
- package/src/core/providers/opendesign-models.ts +628 -0
- package/src/core/providers/opendesign.ts +474 -0
- package/src/core/quota/calibration.ts +166 -0
- package/src/core/quota/commandcode.ts +471 -0
- package/src/core/quota/credentials.ts +490 -0
- package/src/core/quota/ledger.ts +1273 -0
- package/src/core/quota/opencode-go.ts +148 -0
- package/src/core/quota/opendesign.ts +1043 -0
- package/src/core/quota/reconcile.ts +155 -0
- package/src/core/quota/report.ts +261 -0
- package/src/core/quota/service.ts +273 -0
- package/src/core/reprompt.ts +48 -0
- package/src/core/routing/engine.ts +919 -0
- package/src/core/types.ts +825 -0
- package/src/core/ui.ts +236 -0
- package/src/core/util.ts +114 -0
- package/src/gui/index.tsx +1670 -0
- package/src/gui/viewmodel.ts +456 -0
- package/src/index.ts +48 -0
- package/src/rpc.ts +204 -0
- package/src/server/controller.ts +3281 -0
- package/src/server/route-tracker.ts +60 -0
- package/src/server/shared-core.ts +671 -0
- package/src/tui/index.tsx +168 -0
package/src/rpc.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Rpc } from "@opencode/plugin/rpc"
|
|
2
|
+
|
|
3
|
+
const objectOutput = { type: "object", additionalProperties: true } as const
|
|
4
|
+
const emptyInput = { type: "object", properties: {}, additionalProperties: false } as const
|
|
5
|
+
const sessionInput = {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: { sessionID: { type: "string" } },
|
|
8
|
+
required: ["sessionID"],
|
|
9
|
+
additionalProperties: false,
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
const method = (input: any) => ({ input, output: objectOutput })
|
|
13
|
+
|
|
14
|
+
export const SmartRouterRpc = Rpc.define({
|
|
15
|
+
id: "smart-router",
|
|
16
|
+
methods: {
|
|
17
|
+
status: method({
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: { sessionID: { type: "string" } },
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
}),
|
|
22
|
+
preview: method({
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
sessionID: { type: "string" },
|
|
26
|
+
text: { type: "string" },
|
|
27
|
+
files: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
28
|
+
},
|
|
29
|
+
required: ["sessionID", "text"],
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
}),
|
|
32
|
+
setMode: method({
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: { mode: { type: "string", enum: ["efficiency", "balanced", "premium"] } },
|
|
35
|
+
required: ["mode"],
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
}),
|
|
38
|
+
setPaused: method({
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: { paused: { type: "boolean" } },
|
|
41
|
+
required: ["paused"],
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
}),
|
|
44
|
+
/**
|
|
45
|
+
* Turn automatic routing on or off for one session.
|
|
46
|
+
*
|
|
47
|
+
* Not the same as disabling the plugin or pausing every session: the router
|
|
48
|
+
* stays loaded, keeps reporting quota and decisions, and simply stops
|
|
49
|
+
* changing the model. The user keeps whatever model they have.
|
|
50
|
+
*/
|
|
51
|
+
setSessionAuto: method({
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: { sessionID: { type: "string" }, auto: { type: "boolean" } },
|
|
54
|
+
required: ["sessionID", "auto"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
}),
|
|
57
|
+
setDryRun: method({
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: { dryRun: { type: "boolean" } },
|
|
60
|
+
required: ["dryRun"],
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
}),
|
|
63
|
+
resumeAuto: method(sessionInput),
|
|
64
|
+
pin: method({
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
sessionID: { type: "string" },
|
|
68
|
+
providerID: { type: "string" },
|
|
69
|
+
modelID: { type: "string" },
|
|
70
|
+
variant: { type: "string" },
|
|
71
|
+
},
|
|
72
|
+
required: ["sessionID", "providerID", "modelID"],
|
|
73
|
+
additionalProperties: false,
|
|
74
|
+
}),
|
|
75
|
+
approvePremium: method(sessionInput),
|
|
76
|
+
denyPremium: method(sessionInput),
|
|
77
|
+
excludeModel: method({
|
|
78
|
+
type: "object",
|
|
79
|
+
properties: {
|
|
80
|
+
providerID: { type: "string" },
|
|
81
|
+
modelID: { type: "string" },
|
|
82
|
+
excluded: { type: "boolean" },
|
|
83
|
+
},
|
|
84
|
+
required: ["providerID", "modelID", "excluded"],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
}),
|
|
87
|
+
setPremiumBudget: method({
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: { usd: { type: "number", minimum: 0, maximum: 100000 } },
|
|
90
|
+
required: ["usd"],
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
}),
|
|
93
|
+
setReserve: method({
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
providerID: { type: "string" },
|
|
97
|
+
monthlyPct: { type: "number", minimum: 0, maximum: 90 },
|
|
98
|
+
weeklyPct: { type: "number", minimum: 0, maximum: 90 },
|
|
99
|
+
fiveHourPct: { type: "number", minimum: 0, maximum: 90 },
|
|
100
|
+
},
|
|
101
|
+
required: ["providerID"],
|
|
102
|
+
additionalProperties: false,
|
|
103
|
+
}),
|
|
104
|
+
/**
|
|
105
|
+
* The intelligence floor in force, and why.
|
|
106
|
+
*
|
|
107
|
+
* Calibration can only be trusted if its result is visible: a number with
|
|
108
|
+
* no explanation is indistinguishable from the hand-picked one it replaced.
|
|
109
|
+
* This previews without enabling anything.
|
|
110
|
+
*/
|
|
111
|
+
floor: method(emptyInput),
|
|
112
|
+
setCalibrateFloor: method({
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: { enabled: { type: "boolean" } },
|
|
115
|
+
required: ["enabled"],
|
|
116
|
+
additionalProperties: false,
|
|
117
|
+
}),
|
|
118
|
+
refreshQuota: method(emptyInput),
|
|
119
|
+
/**
|
|
120
|
+
* What this machine has, for the first-run screen.
|
|
121
|
+
*
|
|
122
|
+
* Onboarding must show evidence, not a questionnaire. The router already
|
|
123
|
+
* detects credentials and imports history at startup; this reports what it
|
|
124
|
+
* found so the first thing a new user sees is the truth about their own
|
|
125
|
+
* machine rather than a form asking them to type it in.
|
|
126
|
+
*/
|
|
127
|
+
onboarding: method(emptyInput),
|
|
128
|
+
/** Mark the first run as seen, so it does not reappear. */
|
|
129
|
+
completeOnboarding: method(emptyInput),
|
|
130
|
+
/**
|
|
131
|
+
* Record what the user's account page showed, so the estimate reflects the
|
|
132
|
+
* whole account rather than only the traffic this router routed.
|
|
133
|
+
*
|
|
134
|
+
* Takes the percentage the page displays, not a dollar figure: asking a user
|
|
135
|
+
* to divide by a cap they may not know is where the arithmetic mistakes come
|
|
136
|
+
* from. The server converts it against the window the page was describing.
|
|
137
|
+
*/
|
|
138
|
+
/**
|
|
139
|
+
* Record the account's balance page, pasted whole.
|
|
140
|
+
*
|
|
141
|
+
* One field, because that is the actual task: select the page, copy, paste.
|
|
142
|
+
* The server reads the plan, the percentage, which window it belongs to, the
|
|
143
|
+
* reset time, the billing period and the wallet out of the text. A form with
|
|
144
|
+
* four boxes asks the user to do that parsing by hand and get it right.
|
|
145
|
+
*/
|
|
146
|
+
recordPlanPage: method({
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
/** The pasted page text. */
|
|
150
|
+
page: { type: "string" },
|
|
151
|
+
/** Apply it without previewing. Defaults to false: the user confirms first. */
|
|
152
|
+
confirm: { type: "boolean" },
|
|
153
|
+
},
|
|
154
|
+
required: ["page"],
|
|
155
|
+
additionalProperties: false,
|
|
156
|
+
}),
|
|
157
|
+
report: method(emptyInput),
|
|
158
|
+
usage: method(emptyInput),
|
|
159
|
+
getConfig: method(emptyInput),
|
|
160
|
+
resetOverrides: method(emptyInput),
|
|
161
|
+
resetMetrics: method(emptyInput),
|
|
162
|
+
refreshIndicator: method(sessionInput),
|
|
163
|
+
diagnostics: method(emptyInput),
|
|
164
|
+
setTitleIndicator: method({
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: { enabled: { type: "boolean" } },
|
|
167
|
+
required: ["enabled"],
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
events: {
|
|
172
|
+
decision: {
|
|
173
|
+
schema: {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties: {
|
|
176
|
+
decision: { type: "object", additionalProperties: true },
|
|
177
|
+
},
|
|
178
|
+
required: ["decision"],
|
|
179
|
+
additionalProperties: false,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
status: {
|
|
183
|
+
schema: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
sessionID: { type: "string" },
|
|
187
|
+
summary: { type: "object", additionalProperties: true },
|
|
188
|
+
},
|
|
189
|
+
required: ["summary"],
|
|
190
|
+
additionalProperties: false,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
quota: {
|
|
194
|
+
schema: {
|
|
195
|
+
type: "object",
|
|
196
|
+
properties: {
|
|
197
|
+
providers: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
198
|
+
},
|
|
199
|
+
required: ["providers"],
|
|
200
|
+
additionalProperties: false,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
})
|