@d3ara1n/pi-model-roles 0.4.0 → 0.6.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/package.json +1 -1
- package/src/api.ts +51 -0
- package/src/index.ts +10 -0
- package/src/types.ts +57 -0
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Exported functions provide type-safe access — consumers never touch globalThis.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { complete as piAiComplete, streamSimple as piAiStreamSimple } from "@earendil-works/pi-ai";
|
|
9
10
|
import type { ModelRolesAPI, ModelRolesConfig, RoleConfig, ResolvedRole } from "./types.ts";
|
|
10
11
|
import { loadRolesConfig } from "./config.ts";
|
|
11
12
|
import { resolveModelForRole, resolveModelForRoleAsync } from "./resolver.ts";
|
|
@@ -147,6 +148,56 @@ export function initModelRolesAPI(
|
|
|
147
148
|
.getAvailable()
|
|
148
149
|
.map((m: { provider: string; id: string }) => `${m.provider}/${m.id}`);
|
|
149
150
|
},
|
|
151
|
+
|
|
152
|
+
async complete(roleName: string, context: any, options?: any): Promise<any> {
|
|
153
|
+
// Resolve model: explicit override wins, else the role's declared model
|
|
154
|
+
// (model=null transparently uses pi's current model).
|
|
155
|
+
const roleConfig = getConfig().roles[roleName] ?? { model: null };
|
|
156
|
+
const model =
|
|
157
|
+
options?.model ??
|
|
158
|
+
resolveModelForRole(roleConfig, state.modelRegistry, state.currentModel).model;
|
|
159
|
+
if (!model) {
|
|
160
|
+
throw new Error(`complete: role "${roleName}" has no available model`);
|
|
161
|
+
}
|
|
162
|
+
// Resolve auth for the model actually used (refreshes OAuth tokens).
|
|
163
|
+
const auth = await state.modelRegistry.getApiKeyAndHeaders(model);
|
|
164
|
+
if (!auth.ok) {
|
|
165
|
+
throw new Error(`complete: auth failed for ${model.provider}/${model.id}: ${auth.error}`);
|
|
166
|
+
}
|
|
167
|
+
// Forward everything except `model` to pi-ai's complete().
|
|
168
|
+
const { model: _omitModel, ...streamOptions } = options ?? {};
|
|
169
|
+
if (auth.apiKey) streamOptions.apiKey = auth.apiKey;
|
|
170
|
+
if (auth.headers) streamOptions.headers = auth.headers;
|
|
171
|
+
// Apply the role's thinking level unless the caller explicitly overrides it.
|
|
172
|
+
if (roleConfig.thinking && streamOptions.reasoningEffort === undefined) {
|
|
173
|
+
streamOptions.reasoningEffort = roleConfig.thinking;
|
|
174
|
+
}
|
|
175
|
+
return piAiComplete(model, context, streamOptions);
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
async streamWithRole(roleName: string, context: any, options?: any): Promise<any> {
|
|
179
|
+
const roleConfig = getConfig().roles[roleName] ?? { model: null };
|
|
180
|
+
const model =
|
|
181
|
+
options?.model ??
|
|
182
|
+
resolveModelForRole(roleConfig, state.modelRegistry, state.currentModel).model;
|
|
183
|
+
if (!model) {
|
|
184
|
+
throw new Error(`streamWithRole: role "${roleName}" has no available model`);
|
|
185
|
+
}
|
|
186
|
+
const auth = await state.modelRegistry.getApiKeyAndHeaders(model);
|
|
187
|
+
if (!auth.ok) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`streamWithRole: auth failed for ${model.provider}/${model.id}: ${auth.error}`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const { model: _omitModel, ...streamOptions } = options ?? {};
|
|
193
|
+
if (auth.apiKey) streamOptions.apiKey = auth.apiKey;
|
|
194
|
+
if (auth.headers) streamOptions.headers = auth.headers;
|
|
195
|
+
// Apply the role's thinking level unless the caller explicitly overrides it.
|
|
196
|
+
if (roleConfig.thinking && streamOptions.reasoning === undefined) {
|
|
197
|
+
streamOptions.reasoning = roleConfig.thinking;
|
|
198
|
+
}
|
|
199
|
+
return piAiStreamSimple(model, context, streamOptions);
|
|
200
|
+
},
|
|
150
201
|
};
|
|
151
202
|
|
|
152
203
|
// Store on globalThis — survives module identity mismatches
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,16 @@ export type {
|
|
|
17
17
|
ModelRolesConfig,
|
|
18
18
|
ThinkingLevel,
|
|
19
19
|
} from "./types.ts";
|
|
20
|
+
// Re-export pi-ai types so consumers depend on model-roles alone, not pi-ai.
|
|
21
|
+
export type {
|
|
22
|
+
Api,
|
|
23
|
+
AssistantMessage,
|
|
24
|
+
AssistantMessageEventStream,
|
|
25
|
+
Context,
|
|
26
|
+
Model,
|
|
27
|
+
ProviderStreamOptions,
|
|
28
|
+
SimpleStreamOptions,
|
|
29
|
+
} from "@earendil-works/pi-ai";
|
|
20
30
|
|
|
21
31
|
export default function registerModelRolesExtension(pi: ExtensionAPI): void {
|
|
22
32
|
pi.on("session_start", async (_event, ctx) => {
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
* Shared types for pi-model-roles.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type {
|
|
6
|
+
Api,
|
|
7
|
+
AssistantMessage,
|
|
8
|
+
AssistantMessageEventStream,
|
|
9
|
+
Context,
|
|
10
|
+
Model,
|
|
11
|
+
ProviderStreamOptions,
|
|
12
|
+
SimpleStreamOptions,
|
|
13
|
+
} from "@earendil-works/pi-ai";
|
|
14
|
+
|
|
5
15
|
/** Thinking level configuration for a role. */
|
|
6
16
|
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
7
17
|
|
|
@@ -97,4 +107,51 @@ export interface ModelRolesAPI {
|
|
|
97
107
|
getCurrentRole(modelId: string): string | undefined;
|
|
98
108
|
/** List all available models from pi's model registry. Returns provider/id strings (e.g. "anthropic/claude-sonnet-4"). */
|
|
99
109
|
listModels(): string[];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Call pi-ai's complete() with auth resolved internally from the role's model.
|
|
113
|
+
*
|
|
114
|
+
* Auth (including OAuth token refresh) is resolved for the model actually
|
|
115
|
+
* used, so callers never handle API keys or headers. The role's
|
|
116
|
+
* {@link RoleConfig.thinking} level is applied as `reasoningEffort` unless
|
|
117
|
+
* the caller sets it explicitly. No fallback, no retry, no error swallowing;
|
|
118
|
+
* throws if the role has no available model or auth resolution fails.
|
|
119
|
+
*
|
|
120
|
+
* @param roleName - Role whose model + auth + thinking to use
|
|
121
|
+
* @param context - Conversation context (systemPrompt + messages)
|
|
122
|
+
* @param options - Stream options forwarded to pi-ai's complete(). Pass
|
|
123
|
+
* `model` to override the role's model (auth is then resolved for that
|
|
124
|
+
* model); pass `reasoningEffort` to override the role's thinking level;
|
|
125
|
+
* all other fields pass through unchanged.
|
|
126
|
+
*/
|
|
127
|
+
complete<TApi extends Api = Api>(
|
|
128
|
+
roleName: string,
|
|
129
|
+
context: Context,
|
|
130
|
+
options?: ProviderStreamOptions & { model?: Model<TApi> },
|
|
131
|
+
): Promise<AssistantMessage>;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Call pi-ai's streamSimple() with auth resolved internally from the role's model.
|
|
135
|
+
*
|
|
136
|
+
* Streaming counterpart to {@link complete}. Same auth resolution (including
|
|
137
|
+
* OAuth token refresh), same thinking-level application (role's
|
|
138
|
+
* {@link RoleConfig.thinking} → `reasoning` unless overridden), and same
|
|
139
|
+
* error semantics (throws on no model / auth failure). Returns the pi-ai
|
|
140
|
+
* event stream — iterate it for events, call `.result()` for the final
|
|
141
|
+
* AssistantMessage.
|
|
142
|
+
*
|
|
143
|
+
* Note: unlike pi-ai's synchronous `streamSimple`, this is async because auth
|
|
144
|
+
* resolution (OAuth token refresh) must complete before the stream starts.
|
|
145
|
+
*
|
|
146
|
+
* @param roleName - Role whose model + auth + thinking to use
|
|
147
|
+
* @param context - Conversation context (systemPrompt + messages)
|
|
148
|
+
* @param options - Stream options forwarded to pi-ai's streamSimple(). Pass
|
|
149
|
+
* `model` to override the role's model; pass `reasoning` to override the
|
|
150
|
+
* role's thinking level; all other fields pass through.
|
|
151
|
+
*/
|
|
152
|
+
streamWithRole<TApi extends Api = Api>(
|
|
153
|
+
roleName: string,
|
|
154
|
+
context: Context,
|
|
155
|
+
options?: SimpleStreamOptions & { model?: Model<TApi> },
|
|
156
|
+
): Promise<AssistantMessageEventStream>;
|
|
100
157
|
}
|