feat(ai): add Models runtime with provider-owned auth (phase 1)
New Models/MutableModels/createModels collection: provider map, async
model listing (best-effort aggregation), getAuth decision tree with
double-checked locked OAuth refresh, stream/complete with per-field
auth merge over lazyStream.
Auth substrate: ProviderAuth { apiKey?, oauth? }, one type-tagged
credential per provider, CredentialStore (read/modify/delete; modify
is the only write path, serialized RMW), OAuthAuth login/refresh/toAuth
split, prompt()/notify() login callbacks, browser-safe default
AuthContext.
types.ts: Provider alias renamed to ProviderId; ApiOptionsMap and
ApiStreamOptions<TApi> for typed per-API stream options; hasApi()
runtime narrowing guard.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import type { Api, AssistantMessage, AssistantMessageEvent, Model } from "../types.ts";
|
||||
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
|
||||
|
||||
function createSetupErrorMessage(model: Model<Api>, error: unknown): AssistantMessage {
|
||||
return {
|
||||
role: "assistant",
|
||||
content: [],
|
||||
api: model.api,
|
||||
provider: model.provider,
|
||||
model: model.id,
|
||||
usage: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
},
|
||||
stopReason: "error",
|
||||
errorMessage: error instanceof Error ? error.message : String(error),
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
function forwardStream(target: AssistantMessageEventStream, source: AsyncIterable<AssistantMessageEvent>): void {
|
||||
(async () => {
|
||||
for await (const event of source) {
|
||||
target.push(event);
|
||||
}
|
||||
target.end();
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream synchronously while running async setup (auth resolution,
|
||||
* lazy module loading) behind it. Setup failures terminate the stream with an
|
||||
* error event.
|
||||
*/
|
||||
export function lazyStream(
|
||||
model: Model<Api>,
|
||||
setup: () => Promise<AsyncIterable<AssistantMessageEvent>>,
|
||||
): AssistantMessageEventStream {
|
||||
const outer = new AssistantMessageEventStream();
|
||||
|
||||
setup()
|
||||
.then((inner) => {
|
||||
forwardStream(outer, inner);
|
||||
})
|
||||
.catch((error) => {
|
||||
const message = createSetupErrorMessage(model, error);
|
||||
outer.push({ type: "error", reason: "error", error: message });
|
||||
outer.end(message);
|
||||
});
|
||||
|
||||
return outer;
|
||||
}
|
||||
Reference in New Issue
Block a user