pi-commandcode-provider 0.4.3 → 0.5.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 +28 -0
- package/CONTRIBUTING.md +18 -0
- package/README.md +100 -72
- package/index.ts +84 -91
- package/package.json +15 -6
- package/scripts/pi-authenticated.mjs +49 -0
- package/scripts/pi-isolated.mjs +78 -0
- package/src/converters.ts +64 -85
- package/src/core.ts +107 -31
- package/src/cost.ts +16 -4
- package/src/json-schema.ts +382 -0
- package/src/models.ts +251 -15
- package/src/overflow.ts +120 -0
- package/src/pricing.ts +226 -0
- package/src/runtime.ts +279 -0
- package/src/types.ts +19 -1
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import type { CommandCodeModel, LoadCommandCodeModelsResult } from "./models.ts"
|
|
2
|
+
|
|
3
|
+
export interface CommandCodeUi {
|
|
4
|
+
notify(message: string, type?: "info" | "warning" | "error"): void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CommandCodeCommandContext {
|
|
8
|
+
ui: CommandCodeUi
|
|
9
|
+
waitForIdle?: () => Promise<void>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CommandCodeRuntimeApi<
|
|
13
|
+
TProviderConfig,
|
|
14
|
+
TContext extends CommandCodeCommandContext,
|
|
15
|
+
> {
|
|
16
|
+
registerProvider(name: string, config: TProviderConfig): void
|
|
17
|
+
registerCommand(
|
|
18
|
+
name: string,
|
|
19
|
+
options: {
|
|
20
|
+
description: string
|
|
21
|
+
handler: (args: string, ctx: TContext) => Promise<void>
|
|
22
|
+
},
|
|
23
|
+
): void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CommandCodeRuntimeOptions<TProviderConfig> {
|
|
27
|
+
endpoint: string
|
|
28
|
+
cachePath: string
|
|
29
|
+
loadModels: () => Promise<LoadCommandCodeModelsResult>
|
|
30
|
+
createProviderConfig: (models: readonly CommandCodeModel[]) => TProviderConfig
|
|
31
|
+
now?: () => number
|
|
32
|
+
logWarning?: (message: string) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CommandCodeRuntimeStatus {
|
|
36
|
+
source: LoadCommandCodeModelsResult["source"]
|
|
37
|
+
modelCount: number
|
|
38
|
+
lastSuccess?: number
|
|
39
|
+
lastAttempt?: number
|
|
40
|
+
cachePath: string
|
|
41
|
+
endpoint: string
|
|
42
|
+
warning?: string
|
|
43
|
+
refreshing: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CommandCodeRefreshResult {
|
|
47
|
+
refreshed: boolean
|
|
48
|
+
source: CommandCodeRuntimeStatus["source"]
|
|
49
|
+
modelCount: number
|
|
50
|
+
warning?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const REDACTED = "[redacted]"
|
|
54
|
+
|
|
55
|
+
function errorMessage(error: unknown): string {
|
|
56
|
+
return error instanceof Error ? error.message : String(error)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function redactUrl(value: string): string {
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(value)
|
|
62
|
+
return `${url.protocol}//${url.host}${url.pathname}`
|
|
63
|
+
} catch {
|
|
64
|
+
return REDACTED
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function redactDiagnosticText(value: string): string {
|
|
69
|
+
const redactedUrls = value.replace(/https?:\/\/[^\s)]+/gi, (match) => redactUrl(match))
|
|
70
|
+
return redactedUrls
|
|
71
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, `Bearer ${REDACTED}`)
|
|
72
|
+
.replace(/\b(?:user|cc)_[A-Za-z0-9_-]{8,}\b/gi, REDACTED)
|
|
73
|
+
.replace(/\b(?:api[-_ ]?key|token|secret|password)\s*[=:]\s*[^\s,;)]+/gi, (match) => {
|
|
74
|
+
const separator = match.match(/\s*[=:]\s*/)?.[0] ?? "="
|
|
75
|
+
return `${match.slice(0, match.indexOf(separator))}${separator}${REDACTED}`
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function redactEndpoint(value: string): string {
|
|
80
|
+
return redactUrl(value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function formatTimestamp(timestamp: number | undefined): string {
|
|
84
|
+
return timestamp === undefined ? "never" : new Date(timestamp).toISOString()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function formatCommandCodeStatus(status: CommandCodeRuntimeStatus): string {
|
|
88
|
+
const lines = [
|
|
89
|
+
`source: ${status.source}`,
|
|
90
|
+
`model count: ${status.modelCount}`,
|
|
91
|
+
`last success: ${formatTimestamp(status.lastSuccess)}`,
|
|
92
|
+
`last attempt: ${formatTimestamp(status.lastAttempt)}`,
|
|
93
|
+
`cache path: ${status.cachePath}`,
|
|
94
|
+
`endpoint: ${redactEndpoint(status.endpoint)}`,
|
|
95
|
+
`refresh: ${status.refreshing ? "in progress" : "idle"}`,
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
lines.push(`warning: ${status.warning ? redactDiagnosticText(status.warning) : "none"}`)
|
|
99
|
+
return lines.join("\n")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class CommandCodeRuntime<TProviderConfig, TContext extends CommandCodeCommandContext> {
|
|
103
|
+
private readonly now: () => number
|
|
104
|
+
private readonly logWarning: (message: string) => void
|
|
105
|
+
private status: CommandCodeRuntimeStatus
|
|
106
|
+
private providerRegistered = false
|
|
107
|
+
private refreshPromise: Promise<CommandCodeRefreshResult> | undefined
|
|
108
|
+
|
|
109
|
+
constructor(
|
|
110
|
+
private readonly pi: CommandCodeRuntimeApi<TProviderConfig, TContext>,
|
|
111
|
+
private readonly options: CommandCodeRuntimeOptions<TProviderConfig>,
|
|
112
|
+
) {
|
|
113
|
+
this.now = options.now ?? Date.now
|
|
114
|
+
this.logWarning = options.logWarning ?? ((message) => console.warn(`[commandcode] ${message}`))
|
|
115
|
+
const initialStatus: CommandCodeRuntimeStatus = {
|
|
116
|
+
source: "empty",
|
|
117
|
+
modelCount: 0,
|
|
118
|
+
cachePath: options.cachePath,
|
|
119
|
+
endpoint: options.endpoint,
|
|
120
|
+
refreshing: false,
|
|
121
|
+
}
|
|
122
|
+
this.status = { ...initialStatus }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getStatus(): CommandCodeRuntimeStatus {
|
|
126
|
+
return { ...this.status }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async initialize(): Promise<void> {
|
|
130
|
+
this.registerCommands()
|
|
131
|
+
await this.refresh()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
refresh(): Promise<CommandCodeRefreshResult> {
|
|
135
|
+
if (this.refreshPromise) return this.refreshPromise
|
|
136
|
+
|
|
137
|
+
const refreshPromise = this.refreshCatalog().finally(() => {
|
|
138
|
+
if (this.refreshPromise === refreshPromise) this.refreshPromise = undefined
|
|
139
|
+
})
|
|
140
|
+
this.refreshPromise = refreshPromise
|
|
141
|
+
return refreshPromise
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async refreshCatalog(): Promise<CommandCodeRefreshResult> {
|
|
145
|
+
this.status = {
|
|
146
|
+
...this.status,
|
|
147
|
+
lastAttempt: this.now(),
|
|
148
|
+
refreshing: true,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const loaded = await this.options.loadModels()
|
|
153
|
+
const warning = loaded.warning ? redactDiagnosticText(loaded.warning) : undefined
|
|
154
|
+
|
|
155
|
+
const shouldRegister =
|
|
156
|
+
!this.providerRegistered ||
|
|
157
|
+
loaded.source === "live" ||
|
|
158
|
+
(this.status.modelCount === 0 && loaded.models.length > 0)
|
|
159
|
+
|
|
160
|
+
if (shouldRegister) {
|
|
161
|
+
this.pi.registerProvider("commandcode", this.options.createProviderConfig(loaded.models))
|
|
162
|
+
this.providerRegistered = true
|
|
163
|
+
|
|
164
|
+
if (loaded.models.length === 0) {
|
|
165
|
+
const preservedWarning = warning ?? "Model catalog refresh returned no models"
|
|
166
|
+
this.status = {
|
|
167
|
+
...this.status,
|
|
168
|
+
source: loaded.source,
|
|
169
|
+
modelCount: 0,
|
|
170
|
+
warning: preservedWarning,
|
|
171
|
+
refreshing: false,
|
|
172
|
+
}
|
|
173
|
+
this.warn(preservedWarning)
|
|
174
|
+
return {
|
|
175
|
+
refreshed: false,
|
|
176
|
+
source: loaded.source,
|
|
177
|
+
modelCount: 0,
|
|
178
|
+
warning: preservedWarning,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.status = {
|
|
183
|
+
...this.status,
|
|
184
|
+
source: loaded.source,
|
|
185
|
+
modelCount: loaded.models.length,
|
|
186
|
+
lastSuccess: this.now(),
|
|
187
|
+
warning,
|
|
188
|
+
refreshing: false,
|
|
189
|
+
}
|
|
190
|
+
if (warning) this.warn(warning)
|
|
191
|
+
return {
|
|
192
|
+
refreshed: true,
|
|
193
|
+
source: loaded.source,
|
|
194
|
+
modelCount: loaded.models.length,
|
|
195
|
+
warning,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const preservedWarning = warning ?? "Model catalog refresh returned no models"
|
|
200
|
+
this.status = {
|
|
201
|
+
...this.status,
|
|
202
|
+
warning: preservedWarning,
|
|
203
|
+
refreshing: false,
|
|
204
|
+
}
|
|
205
|
+
this.warn(preservedWarning)
|
|
206
|
+
return {
|
|
207
|
+
refreshed: false,
|
|
208
|
+
source: this.status.source,
|
|
209
|
+
modelCount: this.status.modelCount,
|
|
210
|
+
warning: preservedWarning,
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const warning = redactDiagnosticText(
|
|
214
|
+
`Could not refresh the Command Code model catalog: ${errorMessage(error)}`,
|
|
215
|
+
)
|
|
216
|
+
this.status = {
|
|
217
|
+
...this.status,
|
|
218
|
+
warning,
|
|
219
|
+
refreshing: false,
|
|
220
|
+
}
|
|
221
|
+
this.warn(warning)
|
|
222
|
+
return {
|
|
223
|
+
refreshed: false,
|
|
224
|
+
source: this.status.source,
|
|
225
|
+
modelCount: this.status.modelCount,
|
|
226
|
+
warning,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private warn(message: string): void {
|
|
232
|
+
try {
|
|
233
|
+
this.logWarning(redactDiagnosticText(message))
|
|
234
|
+
} catch {
|
|
235
|
+
// Diagnostics must never make a catalog refresh fail.
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private registerCommands(): void {
|
|
240
|
+
this.pi.registerCommand("commandcode-refresh", {
|
|
241
|
+
description: "Refresh the Command Code model catalog",
|
|
242
|
+
handler: async (_args, ctx) => {
|
|
243
|
+
await ctx.waitForIdle?.()
|
|
244
|
+
const result = await this.refresh()
|
|
245
|
+
if (result.refreshed) {
|
|
246
|
+
ctx.ui.notify(
|
|
247
|
+
`Command Code model catalog refreshed (${result.modelCount} models from ${result.source}).`,
|
|
248
|
+
"info",
|
|
249
|
+
)
|
|
250
|
+
} else {
|
|
251
|
+
ctx.ui.notify(
|
|
252
|
+
`Command Code model catalog unchanged (${result.modelCount} models remain available).${result.warning ? ` ${result.warning}` : ""}`,
|
|
253
|
+
"warning",
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
this.pi.registerCommand("commandcode-status", {
|
|
260
|
+
description: "Show redacted Command Code provider diagnostics",
|
|
261
|
+
handler: async (_args, ctx) => {
|
|
262
|
+
ctx.ui.notify(
|
|
263
|
+
formatCommandCodeStatus(this.status),
|
|
264
|
+
this.status.warning ? "warning" : "info",
|
|
265
|
+
)
|
|
266
|
+
},
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function createCommandCodeRuntime<
|
|
272
|
+
TProviderConfig,
|
|
273
|
+
TContext extends CommandCodeCommandContext,
|
|
274
|
+
>(
|
|
275
|
+
pi: CommandCodeRuntimeApi<TProviderConfig, TContext>,
|
|
276
|
+
options: CommandCodeRuntimeOptions<TProviderConfig>,
|
|
277
|
+
): CommandCodeRuntime<TProviderConfig, TContext> {
|
|
278
|
+
return new CommandCodeRuntime(pi, options)
|
|
279
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface Usage {
|
|
|
15
15
|
output: number
|
|
16
16
|
cacheRead: number
|
|
17
17
|
cacheWrite: number
|
|
18
|
+
cacheWrite1h?: number
|
|
18
19
|
totalTokens: number
|
|
19
20
|
cost: UsageCost
|
|
20
21
|
}
|
|
@@ -50,19 +51,34 @@ export interface AssistantMessageLike {
|
|
|
50
51
|
timestamp: number
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
export interface
|
|
54
|
+
export interface ModelCostRates {
|
|
54
55
|
input: number
|
|
55
56
|
output: number
|
|
56
57
|
cacheRead: number
|
|
57
58
|
cacheWrite: number
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
export interface ModelCostTier extends ModelCostRates {
|
|
62
|
+
inputTokensAbove: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ModelCost extends ModelCostRates {
|
|
66
|
+
tiers?: readonly ModelCostTier[]
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
export interface ModelLike {
|
|
61
70
|
id: string
|
|
62
71
|
api: unknown
|
|
63
72
|
provider: string
|
|
64
73
|
maxTokens: number
|
|
65
74
|
cost: ModelCost
|
|
75
|
+
reasoning?: boolean
|
|
76
|
+
thinkingLevelMap?: Partial<Record<string, string | null>>
|
|
77
|
+
thinking?: {
|
|
78
|
+
mode?: "effort"
|
|
79
|
+
effortMap?: Partial<Record<string, string>>
|
|
80
|
+
efforts?: readonly string[]
|
|
81
|
+
}
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
export interface MessageLike {
|
|
@@ -95,6 +111,8 @@ export interface StreamOptions {
|
|
|
95
111
|
signal?: AbortSignal
|
|
96
112
|
headers?: Record<string, string>
|
|
97
113
|
maxTokens?: number
|
|
114
|
+
/** Resolved pi thinking level; forwarded only through the model's map. */
|
|
115
|
+
reasoning?: string
|
|
98
116
|
onPayload?: (payload: unknown, model: ModelLike) => unknown | Promise<unknown>
|
|
99
117
|
onResponse?: (response: ProviderResponseInfo, model: ModelLike) => void | Promise<void>
|
|
100
118
|
/**
|