@ai-ecoverse/slicc-agent 3.35.0 → 3.36.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.
@@ -0,0 +1,109 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>SLICC sign-in</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light dark;
10
+ --background: #ffffff;
11
+ --text: #222222;
12
+ --muted: #6d6d6d;
13
+ --good: #007a4d;
14
+ --bad: #d31510;
15
+ font-family: "Adobe Clean", system-ui, -apple-system, "Segoe UI", sans-serif;
16
+ }
17
+ :root[data-theme="dark"] {
18
+ --background: #1d1d1d;
19
+ --text: #f2f2f2;
20
+ --muted: #aeaeae;
21
+ --good: #2dbf80;
22
+ --bad: #ff7065;
23
+ }
24
+ body {
25
+ margin: 0;
26
+ min-height: 100vh;
27
+ display: grid;
28
+ place-items: center;
29
+ background: var(--background);
30
+ color: var(--text);
31
+ }
32
+ main {
33
+ max-width: 22rem;
34
+ padding: 2rem;
35
+ text-align: center;
36
+ }
37
+ .mark {
38
+ width: 2.5rem;
39
+ height: 2.5rem;
40
+ border-radius: 50%;
41
+ margin: 0 auto 1rem;
42
+ display: grid;
43
+ place-items: center;
44
+ color: var(--background);
45
+ font-size: 1.25rem;
46
+ font-weight: 700;
47
+ background: var(--good);
48
+ }
49
+ [data-state="failed"] .mark {
50
+ background: var(--bad);
51
+ }
52
+ h1 {
53
+ font-size: 1.25rem;
54
+ margin: 0 0 0.5rem;
55
+ }
56
+ p {
57
+ margin: 0;
58
+ color: var(--muted);
59
+ line-height: 1.5;
60
+ }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <main data-state="signing-in">
65
+ <div class="mark" aria-hidden="true">✓</div>
66
+ <h1>Signing in…</h1>
67
+ <p>Handing the sign-in back to SLICC.</p>
68
+ </main>
69
+ <script type="module">
70
+ const theme = (() => {
71
+ try {
72
+ const color = JSON.parse(localStorage.getItem('slicc-agent.settings') ?? '{}').color;
73
+ if (color === 'light' || color === 'dark') return color;
74
+ } catch {}
75
+ return matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
76
+ })();
77
+ document.documentElement.dataset.theme = theme;
78
+ const params = new URLSearchParams(location.search);
79
+ const stay = new URLSearchParams(location.hash.slice(1)).has('stay');
80
+ const answer = {};
81
+ for (const key of ['code', 'state', 'iss', 'error', 'error_description'])
82
+ if (params.has(key)) answer[key] = params.get(key);
83
+ history.replaceState(null, '', location.pathname);
84
+ const main = document.querySelector('main');
85
+ const show = (state, title, text, mark) => {
86
+ main.dataset.state = state;
87
+ main.querySelector('h1').textContent = title;
88
+ main.querySelector('p').textContent = text;
89
+ main.querySelector('.mark').textContent = mark;
90
+ };
91
+ const named = answer.state ? localStorage.getItem(`slicc-mcp-oauth:${answer.state}`) : null;
92
+ const where = named ? ` to ${named}` : '';
93
+ if (!answer.state)
94
+ show(
95
+ 'failed',
96
+ 'Nothing to sign in',
97
+ 'This page only works as the end of a sign-in from SLICC.',
98
+ '!'
99
+ );
100
+ else {
101
+ new BroadcastChannel('slicc-mcp-oauth').postMessage(answer);
102
+ if (answer.error)
103
+ show('failed', `Couldn't sign in${where}`, answer.error_description ?? answer.error, '!');
104
+ else show('done', `Signed in${where}`, 'You can close this window.', '✓');
105
+ if (!stay) setTimeout(() => window.close(), 1500);
106
+ }
107
+ </script>
108
+ </body>
109
+ </html>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/services.ts"],
4
- "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill';\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
- "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AA4IA,MAAM,eAAe,cAA4B,qBAAqB;AAuBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,gBAAgB;AAsDtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
4
+ "sourcesContent": ["import {\n type Context,\n defineService,\n type JsonValue,\n type ReplicatedState,\n} from '@earendil-works/chord';\nimport type { ConversationView } from '@earendil-works/pi-durable';\nimport type { ChangesView } from './changes/git.ts';\nimport type { FrozenCone } from './freezer/index.ts';\nimport type { MemoryEntry, MemoryScope, MemoryTag } from './memory/format.ts';\nimport type { AgentRemote } from './remote/host.ts';\nimport type { Sprinkle } from './sprinkles/kind.ts';\n\nexport type SendMode = 'steer' | 'followUp' | 'reject';\n\nexport interface SendRequest {\n text: string;\n whenBusy: SendMode;\n requestId: string | null;\n agentId?: string | null;\n}\n\nexport interface OperationError {\n code: string;\n message: string;\n}\n\nexport type SendResponse =\n | { accepted: true; submissionId: string; error: null }\n | { accepted: false; submissionId: null; error: OperationError };\n\nexport type PromptResult =\n | { status: 'done'; text: string; reason: null }\n | { status: 'unanswered'; text: null; reason: string };\n\nexport interface AgentControl {\n send(request: SendRequest, context: Context): Promise<SendResponse>;\n wait(submissionId: string, context: Context): Promise<PromptResult>;\n withdraw(\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n abort(context: Context): Promise<void>;\n compact(instructions: string | null, context: Context): Promise<SendResponse>;\n compactAgent(\n agentId: string,\n instructions: string | null,\n context: Context\n ): Promise<SendResponse>;\n reset(handoff: string | null, context: Context): Promise<void>;\n configure(change: AgentSettingsChange, context: Context): Promise<void>;\n rewind(messageId: string | null, context: Context): Promise<Rewound>;\n rewindAgent(agentId: string, messageId: string | null, context: Context): Promise<Rewound>;\n resolveLick(\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n resolveAgentLick(\n agentId: string,\n lickId: string,\n state: 'confirmed' | 'dismissed',\n context: Context\n ): Promise<LickResolved>;\n webhook(\n name: string,\n delivery: WebhookRequest,\n context: Context\n ): Promise<{ delivered: boolean }>;\n stopAgent(agentId: string, context: Context): Promise<void>;\n selectCone(agentId: string, context: Context): Promise<void>;\n createCone(name: string, context: Context): Promise<Created>;\n createScoop(parentId: string, name: string, context: Context): Promise<Created>;\n drop(agentId: string, context: Context): Promise<Created>;\n unqueue(\n agentId: string | null,\n submissionId: string,\n context: Context\n ): Promise<{ outcome: 'withdrawn' | 'already_placed' | 'not_found' }>;\n sprinkleSend(\n sprinkleId: string,\n payload: { action: string; data: JsonValue | null; target: string | null },\n context: Context\n ): Promise<{ delivered: boolean }>;\n sprinkleCall(\n sprinkleId: string,\n method: string,\n args: JsonValue[],\n context: Context\n ): Promise<JsonValue>;\n memorySave(draft: MemoryDraft, context: Context): Promise<MemoryEntry>;\n memoryRemove(id: string, context: Context): Promise<{ removed: boolean }>;\n freeze(agentId: string, context: Context): Promise<Created>;\n newChat(agentId: string, context: Context): Promise<Created>;\n thaw(frozenId: string, context: Context): Promise<Created>;\n discard(frozenId: string, context: Context): Promise<Created>;\n changesOpen(context: Context): Promise<void>;\n changeAccept(path: string, context: Context): Promise<Created>;\n changeRevert(path: string, context: Context): Promise<Created>;\n}\n\nexport interface MemoryDraft {\n id: string | null;\n scope: string;\n section: string;\n title: string;\n body: string;\n tag: MemoryTag | null;\n}\n\nexport type Created = { id: string; error: null } | { id: null; error: string };\n\nexport interface AgentSummary {\n id: string;\n name: string;\n kind: 'cone' | 'scoop';\n parentId: string | null;\n role: string | null;\n}\n\nexport interface AgentsSummary {\n active: string;\n agents: AgentSummary[];\n}\n\nexport type LickResolved =\n | { done: true; text: string; error: null }\n | { done: false; text: null; error: string };\n\nexport interface WebhookRequest {\n id: string | null;\n headers: Record<string, string>;\n body: string;\n}\n\nexport type Rewound =\n | { done: true; text: string; reason: null }\n | { done: false; text: null; reason: 'busy' | 'no-turn' };\n\nexport interface AgentSettingsChange {\n model: { provider: string; modelId: string } | null;\n thinkingLevel: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;\n agentId?: string | null;\n}\n\nexport const AgentControl = defineService<AgentControl>('slicc.agent.control');\n\nexport type Delivered = 'run' | 'steer' | 'follow-up';\n\nexport interface AgentTranscript {\n readonly state: ReplicatedState<ConversationView>;\n readonly deliveries: ReplicatedState<Record<string, Delivered>>;\n readonly agents: ReplicatedState<AgentsSummary>;\n readonly views: ReplicatedState<Record<string, ConversationView>>;\n readonly commands: ReplicatedState<Command[]>;\n readonly sprinkles: ReplicatedState<Sprinkle[]>;\n readonly memories: ReplicatedState<MemoryEntry[]>;\n readonly memoryScopes: ReplicatedState<MemoryScope[]>;\n readonly frozen: ReplicatedState<FrozenCone[]>;\n readonly changes: ReplicatedState<ChangesView>;\n}\n\nexport interface Command {\n name: string;\n description: string;\n kind: 'prompt' | 'skill';\n}\n\nexport const AgentTranscript = defineService<AgentTranscript>('slicc.agent.transcript');\n\nexport interface AgentSessions {\n attach(sessionId: string, context: Context): Promise<void>;\n detach(context: Context): Promise<void>;\n}\n\nexport const AgentSessions = defineService<AgentSessions>('slicc.agent.sessions');\n\nexport const AgentRemoteService = defineService<AgentRemote>('slicc.agent.remote');\n\nexport const AGENT_SESSION = 'agent';\n\nexport interface ModelChoice {\n id: string;\n label: string;\n provider: string;\n kind: 'chat' | 'classifier';\n reasoning: boolean;\n images?: boolean;\n contextWindow: number;\n}\n\nexport interface AccountState {\n id: string;\n provider: string;\n identity: string;\n status: 'connected' | 'disconnected' | 'expired';\n auth: 'api-key' | 'oauth';\n needs: 'cors-free-transport' | null;\n group?: 'providers' | 'mcp';\n secret?: 'api-key' | 'token';\n}\n\nexport interface BudgetState {\n provider: string;\n percent: number;\n window: 'weekly';\n resets: string;\n}\n\nexport interface SignIn {\n clientId: string;\n scopes: string;\n imsEnvironment: string;\n}\n\nexport interface McpSignIn {\n url: string;\n state: string;\n}\n\nexport interface SettingsState {\n models: ModelChoice[];\n accounts: AccountState[];\n budget?: BudgetState | null;\n}\n\nexport interface AgentSettings {\n readonly state: ReplicatedState<SettingsState>;\n connect(\n providerId: string,\n secret: string,\n region: string | null,\n context: Context\n ): Promise<void>;\n disconnect(providerId: string, context: Context): Promise<void>;\n signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;\n}\n\nexport const AgentSettings = defineService<AgentSettings>('slicc.agent.settings');\n"],
5
+ "mappings": "AAAA;AAAA,EAEE;AAAA,OAGK;AA4IA,MAAM,eAAe,cAA4B,qBAAqB;AAuBtE,MAAM,kBAAkB,cAA+B,wBAAwB;AAO/E,MAAM,gBAAgB,cAA6B,sBAAsB;AAEzE,MAAM,qBAAqB,cAA2B,oBAAoB;AAE1E,MAAM,gBAAgB;AA2DtB,MAAM,gBAAgB,cAA6B,sBAAsB;",
6
6
  "names": []
7
7
  }
package/dist/settings.js CHANGED
@@ -1,4 +1,4 @@
1
- import { replicatedState } from "@earendil-works/chord";
1
+ import { RemoteServiceError, replicatedState } from "@earendil-works/chord";
2
2
  import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3
3
  import { createModels } from "@earendil-works/pi-ai/models";
4
4
  import { amazonBedrockProvider } from "@earendil-works/pi-ai/providers/amazon-bedrock";
@@ -62,6 +62,16 @@ function accounts(stored, providers) {
62
62
  needs: corsFree.has(provider.id) ? "cors-free-transport" : null
63
63
  }));
64
64
  }
65
+ async function shown(run) {
66
+ try {
67
+ return await run();
68
+ } catch (error) {
69
+ throw new RemoteServiceError(
70
+ "service_invalid_value",
71
+ error instanceof Error ? error.message : String(error)
72
+ );
73
+ }
74
+ }
65
75
  async function budget(credentials, usage) {
66
76
  const credential = await credentials.read(ADOBE);
67
77
  const key = credential?.type === "api_key" ? credential.key : void 0;
@@ -105,7 +115,7 @@ async function createAgentSettings(models, credentials, providers = sliccProvide
105
115
  async connect(providerId, secret, region, context) {
106
116
  const server = routed(providerId);
107
117
  if (server) {
108
- await server.connect(providerId, secret);
118
+ await shown(() => server.connect(providerId, secret));
109
119
  await refresh(context);
110
120
  return;
111
121
  }
@@ -118,6 +128,8 @@ async function createAgentSettings(models, credentials, providers = sliccProvide
118
128
  await refresh(context);
119
129
  },
120
130
  async signIn(providerId) {
131
+ const server = routed(providerId);
132
+ if (server) return shown(() => server.signIn(providerId));
121
133
  if (providerId !== ADOBE) return null;
122
134
  const { clientId, scopes, imsEnvironment } = await adobeConfig(
123
135
  endpoint,
@@ -139,6 +151,7 @@ export {
139
151
  DEFAULT_REGIONS,
140
152
  createAgentSettings,
141
153
  createSliccModels,
154
+ shown,
142
155
  sliccProviders,
143
156
  withDefaultRegions
144
157
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/settings.ts"],
4
- "sourcesContent": ["import { type Context, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { CredentialStore, Provider } from '@earendil-works/pi-ai';\nimport { createModels, type MutableModels } from '@earendil-works/pi-ai/models';\nimport { amazonBedrockProvider } from '@earendil-works/pi-ai/providers/amazon-bedrock';\nimport {\n ADOBE,\n ADOBE_PROXY,\n adobeConfig,\n adobeProvider,\n adobeUsage,\n type Budget,\n} from './adobe.ts';\nimport type { McpAccounts } from './mcp/index.ts';\nimport { MCP_ACCOUNT } from './mcp/label.ts';\nimport type { AccountState, AgentSettings, ModelChoice, SettingsState } from './services.ts';\n\nexport const BEDROCK = 'amazon-bedrock';\nexport const DEFAULT_MODEL = { provider: BEDROCK, modelId: 'us.anthropic.claude-sonnet-5-5' };\nexport const DEFAULT_REGIONS: Readonly<Record<string, string>> = { [BEDROCK]: 'us-west-2' };\n\nconst corsFree = new Set([BEDROCK, ADOBE]);\nconst signIn = new Set([ADOBE]);\nconst BUDGET_EVERY = 5 * 60 * 1000;\n\nexport function sliccProviders(): Provider[] {\n return [amazonBedrockProvider() as Provider, adobeProvider()];\n}\n\nexport function withDefaultRegions(credentials: CredentialStore): CredentialStore {\n return {\n async read(providerId, options) {\n const credential = await credentials.read(providerId, options);\n const region = DEFAULT_REGIONS[providerId];\n if (credential?.type !== 'api_key' || !region || credential.env?.AWS_REGION) {\n return credential;\n }\n return { ...credential, env: { ...credential.env, AWS_REGION: region } };\n },\n list: (options) => credentials.list(options),\n modify: (providerId, fn, options) => credentials.modify(providerId, fn, options),\n delete: (providerId, options) => credentials.delete(providerId, options),\n };\n}\n\nexport function createSliccModels(\n credentials: CredentialStore,\n providers = sliccProviders()\n): MutableModels {\n const models = createModels({ credentials: withDefaultRegions(credentials) });\n for (const provider of providers) models.setProvider(provider);\n return models;\n}\n\nfunction choices(\n models: MutableModels,\n providers: Provider[],\n stored: ReadonlySet<string>\n): ModelChoice[] {\n return providers\n .filter((provider) => stored.has(provider.id))\n .flatMap((provider) =>\n models.getModels(provider.id).map((model) => ({\n id: `${provider.id}/${model.id}`,\n label: model.name,\n provider: provider.name ?? provider.id,\n kind: 'chat' as const,\n reasoning: Boolean(model.reasoning),\n images: model.input.includes('image'),\n contextWindow: model.contextWindow,\n }))\n );\n}\n\nfunction accounts(stored: ReadonlySet<string>, providers: Provider[]): AccountState[] {\n return providers.map((provider) => ({\n id: provider.id,\n provider: provider.name ?? provider.id,\n identity: stored.has(provider.id) ? (signIn.has(provider.id) ? 'Adobe ID' : 'API key') : '',\n status: stored.has(provider.id) ? 'connected' : 'disconnected',\n auth: signIn.has(provider.id) ? 'oauth' : 'api-key',\n needs: corsFree.has(provider.id) ? 'cors-free-transport' : null,\n }));\n}\n\nexport type UsageReader = (token: string) => Promise<Budget | null>;\n\nasync function budget(credentials: CredentialStore, usage: UsageReader) {\n const credential = await credentials.read(ADOBE);\n const key = credential?.type === 'api_key' ? credential.key : undefined;\n const found = key ? await usage(key).catch(() => null) : null;\n return found ? { provider: ADOBE, ...found } : null;\n}\n\nexport async function createAgentSettings(\n models: MutableModels,\n credentials: CredentialStore,\n providers = sliccProviders(),\n options: { usage?: UsageReader; every?: number; mcp?: McpAccounts } = {}\n): Promise<AgentSettings> {\n const { mcp } = options;\n const routed = (providerId: string) => (providerId.startsWith(MCP_ACCOUNT) ? mcp : undefined);\n const endpoint = providers.find((provider) => provider.id === ADOBE)?.baseUrl ?? ADOBE_PROXY;\n const usage = options.usage ?? ((token: string) => adobeUsage(token, { endpoint }));\n const snapshot = async (): Promise<SettingsState> => {\n const stored = new Set((await credentials.list()).map((info) => info.providerId));\n return {\n models: choices(models, providers, stored),\n accounts: [...accounts(stored, providers), ...((await mcp?.accounts()) ?? [])],\n budget: await budget(credentials, usage),\n };\n };\n const state = replicatedState<SettingsState>(await snapshot());\n let latest = Promise.resolve();\n const refresh = (context: Context) => {\n const next = latest.then(async () => state.replace(context, await snapshot()));\n latest = next.catch(() => undefined);\n return next;\n };\n const signedIn = providers\n .filter((provider) => signIn.has(provider.id))\n .map((provider) => provider.id);\n const discover = (ids: string[]) =>\n models.refresh({ providers: ids }).then(\n () => undefined,\n () => undefined\n );\n if (signedIn.length) {\n const cycle = () =>\n void discover(signedIn)\n .then(() => refresh(BACKGROUND_CONTEXT))\n .catch(() => undefined);\n cycle();\n const timer: unknown = setInterval(cycle, options.every ?? BUDGET_EVERY);\n (timer as { unref?: () => void }).unref?.();\n }\n mcp?.onChange(() => void refresh(BACKGROUND_CONTEXT).catch(() => undefined));\n return {\n state,\n async connect(providerId, secret, region, context) {\n const server = routed(providerId);\n if (server) {\n await server.connect(providerId, secret);\n await refresh(context);\n return;\n }\n await credentials.modify(providerId, async () => ({\n type: 'api_key',\n key: secret,\n ...(region ? { env: { AWS_REGION: region } } : {}),\n }));\n if (signIn.has(providerId)) await discover([providerId]);\n await refresh(context);\n },\n async signIn(providerId) {\n if (providerId !== ADOBE) return null;\n const { clientId, scopes, imsEnvironment } = await adobeConfig(endpoint, (input, init) =>\n globalThis.fetch(input, init)\n );\n return { clientId, scopes, imsEnvironment: imsEnvironment || 'prod' };\n },\n async disconnect(providerId, context) {\n const server = routed(providerId);\n if (server) await server.disconnect(providerId);\n else await credentials.delete(providerId);\n await refresh(context);\n },\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { type Context, RemoteServiceError, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { CredentialStore, Provider } from '@earendil-works/pi-ai';\nimport { createModels, type MutableModels } from '@earendil-works/pi-ai/models';\nimport { amazonBedrockProvider } from '@earendil-works/pi-ai/providers/amazon-bedrock';\nimport {\n ADOBE,\n ADOBE_PROXY,\n adobeConfig,\n adobeProvider,\n adobeUsage,\n type Budget,\n} from './adobe.ts';\nimport type { McpAccounts } from './mcp/index.ts';\nimport { MCP_ACCOUNT } from './mcp/label.ts';\nimport type { AccountState, AgentSettings, ModelChoice, SettingsState } from './services.ts';\n\nexport const BEDROCK = 'amazon-bedrock';\nexport const DEFAULT_MODEL = { provider: BEDROCK, modelId: 'us.anthropic.claude-sonnet-5-5' };\nexport const DEFAULT_REGIONS: Readonly<Record<string, string>> = { [BEDROCK]: 'us-west-2' };\n\nconst corsFree = new Set([BEDROCK, ADOBE]);\nconst signIn = new Set([ADOBE]);\nconst BUDGET_EVERY = 5 * 60 * 1000;\n\nexport function sliccProviders(): Provider[] {\n return [amazonBedrockProvider() as Provider, adobeProvider()];\n}\n\nexport function withDefaultRegions(credentials: CredentialStore): CredentialStore {\n return {\n async read(providerId, options) {\n const credential = await credentials.read(providerId, options);\n const region = DEFAULT_REGIONS[providerId];\n if (credential?.type !== 'api_key' || !region || credential.env?.AWS_REGION) {\n return credential;\n }\n return { ...credential, env: { ...credential.env, AWS_REGION: region } };\n },\n list: (options) => credentials.list(options),\n modify: (providerId, fn, options) => credentials.modify(providerId, fn, options),\n delete: (providerId, options) => credentials.delete(providerId, options),\n };\n}\n\nexport function createSliccModels(\n credentials: CredentialStore,\n providers = sliccProviders()\n): MutableModels {\n const models = createModels({ credentials: withDefaultRegions(credentials) });\n for (const provider of providers) models.setProvider(provider);\n return models;\n}\n\nfunction choices(\n models: MutableModels,\n providers: Provider[],\n stored: ReadonlySet<string>\n): ModelChoice[] {\n return providers\n .filter((provider) => stored.has(provider.id))\n .flatMap((provider) =>\n models.getModels(provider.id).map((model) => ({\n id: `${provider.id}/${model.id}`,\n label: model.name,\n provider: provider.name ?? provider.id,\n kind: 'chat' as const,\n reasoning: Boolean(model.reasoning),\n images: model.input.includes('image'),\n contextWindow: model.contextWindow,\n }))\n );\n}\n\nfunction accounts(stored: ReadonlySet<string>, providers: Provider[]): AccountState[] {\n return providers.map((provider) => ({\n id: provider.id,\n provider: provider.name ?? provider.id,\n identity: stored.has(provider.id) ? (signIn.has(provider.id) ? 'Adobe ID' : 'API key') : '',\n status: stored.has(provider.id) ? 'connected' : 'disconnected',\n auth: signIn.has(provider.id) ? 'oauth' : 'api-key',\n needs: corsFree.has(provider.id) ? 'cors-free-transport' : null,\n }));\n}\n\nexport async function shown<T>(run: () => Promise<T>): Promise<T> {\n try {\n return await run();\n } catch (error) {\n throw new RemoteServiceError(\n 'service_invalid_value',\n error instanceof Error ? error.message : String(error)\n );\n }\n}\n\nexport type UsageReader = (token: string) => Promise<Budget | null>;\n\nasync function budget(credentials: CredentialStore, usage: UsageReader) {\n const credential = await credentials.read(ADOBE);\n const key = credential?.type === 'api_key' ? credential.key : undefined;\n const found = key ? await usage(key).catch(() => null) : null;\n return found ? { provider: ADOBE, ...found } : null;\n}\n\nexport async function createAgentSettings(\n models: MutableModels,\n credentials: CredentialStore,\n providers = sliccProviders(),\n options: { usage?: UsageReader; every?: number; mcp?: McpAccounts } = {}\n): Promise<AgentSettings> {\n const { mcp } = options;\n const routed = (providerId: string) => (providerId.startsWith(MCP_ACCOUNT) ? mcp : undefined);\n const endpoint = providers.find((provider) => provider.id === ADOBE)?.baseUrl ?? ADOBE_PROXY;\n const usage = options.usage ?? ((token: string) => adobeUsage(token, { endpoint }));\n const snapshot = async (): Promise<SettingsState> => {\n const stored = new Set((await credentials.list()).map((info) => info.providerId));\n return {\n models: choices(models, providers, stored),\n accounts: [...accounts(stored, providers), ...((await mcp?.accounts()) ?? [])],\n budget: await budget(credentials, usage),\n };\n };\n const state = replicatedState<SettingsState>(await snapshot());\n let latest = Promise.resolve();\n const refresh = (context: Context) => {\n const next = latest.then(async () => state.replace(context, await snapshot()));\n latest = next.catch(() => undefined);\n return next;\n };\n const signedIn = providers\n .filter((provider) => signIn.has(provider.id))\n .map((provider) => provider.id);\n const discover = (ids: string[]) =>\n models.refresh({ providers: ids }).then(\n () => undefined,\n () => undefined\n );\n if (signedIn.length) {\n const cycle = () =>\n void discover(signedIn)\n .then(() => refresh(BACKGROUND_CONTEXT))\n .catch(() => undefined);\n cycle();\n const timer: unknown = setInterval(cycle, options.every ?? BUDGET_EVERY);\n (timer as { unref?: () => void }).unref?.();\n }\n mcp?.onChange(() => void refresh(BACKGROUND_CONTEXT).catch(() => undefined));\n return {\n state,\n async connect(providerId, secret, region, context) {\n const server = routed(providerId);\n if (server) {\n await shown(() => server.connect(providerId, secret));\n await refresh(context);\n return;\n }\n await credentials.modify(providerId, async () => ({\n type: 'api_key',\n key: secret,\n ...(region ? { env: { AWS_REGION: region } } : {}),\n }));\n if (signIn.has(providerId)) await discover([providerId]);\n await refresh(context);\n },\n async signIn(providerId) {\n const server = routed(providerId);\n if (server) return shown(() => server.signIn(providerId));\n if (providerId !== ADOBE) return null;\n const { clientId, scopes, imsEnvironment } = await adobeConfig(endpoint, (input, init) =>\n globalThis.fetch(input, init)\n );\n return { clientId, scopes, imsEnvironment: imsEnvironment || 'prod' };\n },\n async disconnect(providerId, context) {\n const server = routed(providerId);\n if (server) await server.disconnect(providerId);\n else await credentials.delete(providerId);\n await refresh(context);\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,73 @@
1
+ const MCP_CHANNEL = "slicc-mcp-oauth";
2
+ const MCP_NAME_KEY = "slicc-mcp-oauth:";
3
+ const SIGN_IN_TIMEOUT_MS = 5 * 60 * 1e3;
4
+ const browser = {
5
+ open: () => globalThis.open?.("about:blank", "slicc-mcp-oauth", "popup,width=520,height=720") ?? null,
6
+ channel: () => new BroadcastChannel(MCP_CHANNEL),
7
+ remember: (state, name) => {
8
+ const key = `${MCP_NAME_KEY}${state}`;
9
+ if (name === null) globalThis.localStorage?.removeItem(key);
10
+ else globalThis.localStorage?.setItem(key, name);
11
+ }
12
+ };
13
+ const LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
14
+ function safeAuthorizationUrl(value) {
15
+ if (!URL.canParse(value)) return false;
16
+ const url = new URL(value);
17
+ return url.protocol === "https:" || url.protocol === "http:" && LOOPBACK.has(url.hostname);
18
+ }
19
+ function answered(channel, state, signal, timeoutMs) {
20
+ return new Promise((resolve, reject) => {
21
+ const done = () => {
22
+ clearTimeout(timer);
23
+ signal.removeEventListener("abort", stop);
24
+ };
25
+ const stop = () => {
26
+ done();
27
+ reject(signal.reason);
28
+ };
29
+ const timer = setTimeout(() => {
30
+ done();
31
+ reject(new Error("The sign-in window gave no answer. Try again."));
32
+ }, timeoutMs);
33
+ signal.addEventListener("abort", stop, { once: true });
34
+ channel.onmessage = ({ data }) => {
35
+ const answer = data;
36
+ if (answer?.state !== state) return;
37
+ done();
38
+ resolve(JSON.stringify(answer));
39
+ };
40
+ });
41
+ }
42
+ function mcpLogin(host = browser) {
43
+ return async (providerId, signIn, options) => {
44
+ const popup = host.open();
45
+ const channel = host.channel();
46
+ let state;
47
+ try {
48
+ const started = await signIn();
49
+ options.signal.throwIfAborted();
50
+ if (!started?.url) throw new Error("This MCP server has no sign-in.");
51
+ if (!safeAuthorizationUrl(started.url))
52
+ throw new Error("The authorization server gave a sign-in address that isn't https.");
53
+ if (!popup)
54
+ throw new Error("The browser blocked the sign-in window. Allow pop-ups for SLICC.");
55
+ state = started.state;
56
+ host.remember?.(state, providerId.replace(/^mcp:/, ""));
57
+ popup.location.href = started.url;
58
+ return await answered(channel, state, options.signal, host.timeoutMs ?? SIGN_IN_TIMEOUT_MS);
59
+ } finally {
60
+ if (state) host.remember?.(state, null);
61
+ channel.close();
62
+ popup?.close();
63
+ }
64
+ };
65
+ }
66
+ export {
67
+ MCP_CHANNEL,
68
+ MCP_NAME_KEY,
69
+ SIGN_IN_TIMEOUT_MS,
70
+ mcpLogin,
71
+ safeAuthorizationUrl
72
+ };
73
+ //# sourceMappingURL=mcp-login.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/spectrum/mcp-login.ts"],
4
+ "sourcesContent": ["import type { McpSignIn, SignIn } from '../services.ts';\n\nexport const MCP_CHANNEL = 'slicc-mcp-oauth';\nexport const MCP_NAME_KEY = 'slicc-mcp-oauth:';\nexport const SIGN_IN_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface Popup {\n location: { href: string };\n close(): void;\n}\n\nexport interface Channel {\n onmessage: ((event: { data: unknown }) => void) | null;\n close(): void;\n}\n\nexport interface McpLoginHost {\n open(): Popup | null;\n channel(): Channel;\n remember?(state: string, name: string | null): void;\n timeoutMs?: number;\n}\n\nconst browser: McpLoginHost = {\n open: () =>\n globalThis.open?.('about:blank', 'slicc-mcp-oauth', 'popup,width=520,height=720') ?? null,\n channel: () => new BroadcastChannel(MCP_CHANNEL) as unknown as Channel,\n remember: (state, name) => {\n const key = `${MCP_NAME_KEY}${state}`;\n if (name === null) globalThis.localStorage?.removeItem(key);\n else globalThis.localStorage?.setItem(key, name);\n },\n};\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);\n\nexport function safeAuthorizationUrl(value: string): boolean {\n if (!URL.canParse(value)) return false;\n const url = new URL(value);\n return url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname));\n}\n\nfunction answered(channel: Channel, state: string, signal: AbortSignal, timeoutMs: number) {\n return new Promise<string>((resolve, reject) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', stop);\n };\n const stop = () => {\n done();\n reject(signal.reason);\n };\n const timer = setTimeout(() => {\n done();\n reject(new Error('The sign-in window gave no answer. Try again.'));\n }, timeoutMs);\n signal.addEventListener('abort', stop, { once: true });\n channel.onmessage = ({ data }) => {\n const answer = data as { state?: unknown } | null;\n if (answer?.state !== state) return;\n done();\n resolve(JSON.stringify(answer));\n };\n });\n}\n\nexport function mcpLogin(host: McpLoginHost = browser) {\n return async (\n providerId: string,\n signIn: () => Promise<SignIn | McpSignIn | null>,\n options: { signal: AbortSignal }\n ): Promise<string> => {\n const popup = host.open();\n const channel = host.channel();\n let state: string | undefined;\n try {\n const started = (await signIn()) as McpSignIn | null;\n options.signal.throwIfAborted();\n if (!started?.url) throw new Error('This MCP server has no sign-in.');\n if (!safeAuthorizationUrl(started.url))\n throw new Error(\"The authorization server gave a sign-in address that isn't https.\");\n if (!popup)\n throw new Error('The browser blocked the sign-in window. Allow pop-ups for SLICC.');\n state = started.state;\n host.remember?.(state, providerId.replace(/^mcp:/, ''));\n popup.location.href = started.url;\n return await answered(channel, state, options.signal, host.timeoutMs ?? SIGN_IN_TIMEOUT_MS);\n } finally {\n if (state) host.remember?.(state, null);\n channel.close();\n popup?.close();\n }\n };\n}\n"],
5
+ "mappings": "AAEO,MAAM,cAAc;AACpB,MAAM,eAAe;AACrB,MAAM,qBAAqB,IAAI,KAAK;AAmB3C,MAAM,UAAwB;AAAA,EAC5B,MAAM,MACJ,WAAW,OAAO,eAAe,mBAAmB,4BAA4B,KAAK;AAAA,EACvF,SAAS,MAAM,IAAI,iBAAiB,WAAW;AAAA,EAC/C,UAAU,CAAC,OAAO,SAAS;AACzB,UAAM,MAAM,GAAG,YAAY,GAAG,KAAK;AACnC,QAAI,SAAS,KAAM,YAAW,cAAc,WAAW,GAAG;AAAA,QACrD,YAAW,cAAc,QAAQ,KAAK,IAAI;AAAA,EACjD;AACF;AAEA,MAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAErD,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,CAAC,IAAI,SAAS,KAAK,EAAG,QAAO;AACjC,QAAM,MAAM,IAAI,IAAI,KAAK;AACzB,SAAO,IAAI,aAAa,YAAa,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ;AAC5F;AAEA,SAAS,SAAS,SAAkB,OAAe,QAAqB,WAAmB;AACzF,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,UAAM,OAAO,MAAM;AACjB,mBAAa,KAAK;AAClB,aAAO,oBAAoB,SAAS,IAAI;AAAA,IAC1C;AACA,UAAM,OAAO,MAAM;AACjB,WAAK;AACL,aAAO,OAAO,MAAM;AAAA,IACtB;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK;AACL,aAAO,IAAI,MAAM,+CAA+C,CAAC;AAAA,IACnE,GAAG,SAAS;AACZ,WAAO,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACrD,YAAQ,YAAY,CAAC,EAAE,KAAK,MAAM;AAChC,YAAM,SAAS;AACf,UAAI,QAAQ,UAAU,MAAO;AAC7B,WAAK;AACL,cAAQ,KAAK,UAAU,MAAM,CAAC;AAAA,IAChC;AAAA,EACF,CAAC;AACH;AAEO,SAAS,SAAS,OAAqB,SAAS;AACrD,SAAO,OACL,YACA,QACA,YACoB;AACpB,UAAM,QAAQ,KAAK,KAAK;AACxB,UAAM,UAAU,KAAK,QAAQ;AAC7B,QAAI;AACJ,QAAI;AACF,YAAM,UAAW,MAAM,OAAO;AAC9B,cAAQ,OAAO,eAAe;AAC9B,UAAI,CAAC,SAAS,IAAK,OAAM,IAAI,MAAM,iCAAiC;AACpE,UAAI,CAAC,qBAAqB,QAAQ,GAAG;AACnC,cAAM,IAAI,MAAM,mEAAmE;AACrF,UAAI,CAAC;AACH,cAAM,IAAI,MAAM,kEAAkE;AACpF,cAAQ,QAAQ;AAChB,WAAK,WAAW,OAAO,WAAW,QAAQ,SAAS,EAAE,CAAC;AACtD,YAAM,SAAS,OAAO,QAAQ;AAC9B,aAAO,MAAM,SAAS,SAAS,OAAO,QAAQ,QAAQ,KAAK,aAAa,kBAAkB;AAAA,IAC5F,UAAE;AACA,UAAI,MAAO,MAAK,WAAW,OAAO,IAAI;AACtC,cAAQ,MAAM;AACd,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -1,5 +1,6 @@
1
1
  import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
2
2
  import { Emitter } from "./emitter.js";
3
+ import { mcpLogin } from "./mcp-login.js";
3
4
  const SETTINGS_KEY = "slicc-agent.settings";
4
5
  const defaults = {
5
6
  color: "system",
@@ -14,10 +15,12 @@ class SettingsAdapter extends Emitter {
14
15
  #agent;
15
16
  #storage;
16
17
  #login;
18
+ #mcpLogin;
17
19
  #signing = /* @__PURE__ */ new Map();
18
20
  #local;
19
- constructor(settings, agent, storage, login = null) {
21
+ constructor(settings, agent, storage, login = null, mcp = mcpLogin()) {
20
22
  super();
23
+ this.#mcpLogin = mcp;
21
24
  this.#settings = settings;
22
25
  this.#agent = agent;
23
26
  this.#storage = storage;
@@ -65,7 +68,7 @@ class SettingsAdapter extends Emitter {
65
68
  }
66
69
  async connect(id, secret, options = {}) {
67
70
  const account = this.#state().accounts.find((candidate) => candidate.id === id);
68
- const login = account?.auth === "oauth" ? this.#login : null;
71
+ const login = account?.auth !== "oauth" ? null : account.group === "mcp" ? this.#mcpLogin : this.#login;
69
72
  const key = secret ?? (login ? await this.#signIn(id, login) : void 0);
70
73
  if (!key)
71
74
  throw new Error(`${id} needs ${account?.auth === "oauth" ? "a sign-in" : "an API key"}`);
@@ -77,9 +80,13 @@ class SettingsAdapter extends Emitter {
77
80
  this.#signing.set(id, controller);
78
81
  this.emit("accounts", this.accounts());
79
82
  try {
80
- const key = await login(id, () => this.#settings.signIn(id, BACKGROUND_CONTEXT), {
81
- signal: controller.signal
82
- });
83
+ const key = await login(
84
+ id,
85
+ () => this.#settings.signIn(id, BACKGROUND_CONTEXT),
86
+ {
87
+ signal: controller.signal
88
+ }
89
+ );
83
90
  controller.signal.throwIfAborted();
84
91
  return key;
85
92
  } finally {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/spectrum/settings-port.ts"],
4
- "sourcesContent": ["import type {\n Account,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AgentSettings, SettingsState, SignIn } from '../services.ts';\nimport type { AgentAdapter } from './agent-port.ts';\nimport { Emitter } from './emitter.ts';\n\nexport const SETTINGS_KEY = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n showThinking: false,\n diffStyle: 'unified',\n};\n\ntype Local = Omit<Settings, 'model' | 'thinking'>;\n\nexport type Login = (\n providerId: string,\n signIn: () => Promise<SignIn | null>,\n options: { signal: AbortSignal }\n) => Promise<string>;\n\nexport class SettingsAdapter extends Emitter<SettingsEvents> implements SettingsPort {\n readonly #settings: AgentSettings;\n readonly #agent: AgentAdapter;\n readonly #storage: Pick<Storage, 'getItem' | 'setItem'> | null;\n readonly #login: Login | null;\n readonly #signing = new Map<string, AbortController>();\n #local: Local;\n\n constructor(\n settings: AgentSettings,\n agent: AgentAdapter,\n storage: Pick<Storage, 'getItem' | 'setItem'> | null,\n login: Login | null = null\n ) {\n super();\n this.#settings = settings;\n this.#agent = agent;\n this.#storage = storage;\n this.#login = login;\n const saved = JSON.parse(storage?.getItem(SETTINGS_KEY) ?? '{}') as Partial<Local>;\n const { model: _model, thinking: _thinking, ...local } = { ...defaults, ...saved };\n this.#local = local;\n settings.state.subscribe(() => {\n this.emit('accounts', this.accounts());\n this.emit('settings', this.get());\n });\n agent.on('agents', () => this.emit('settings', this.get()));\n }\n\n #state(): SettingsState {\n return this.#settings.state.value ?? { models: [], accounts: [] };\n }\n\n get(): Settings {\n const cone = this.#agent.list()[0];\n return { ...this.#local, model: cone?.model ?? '', thinking: cone?.thinking ?? 'off' };\n }\n\n update(patch: Partial<Settings>): void {\n const { model, thinking, ...local } = patch;\n const id = this.#agent.active();\n if (model !== undefined) this.#agent.setModel(id, model);\n if (thinking !== undefined) this.#agent.setThinking(id, thinking);\n this.#local = { ...this.#local, ...local };\n this.#storage?.setItem(SETTINGS_KEY, JSON.stringify(this.#local));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n return this.#state().models.map(({ id, label, provider, kind, reasoning, images }) => ({\n id,\n label,\n provider,\n kind,\n reasoning,\n ...(images === undefined ? {} : { images }),\n }));\n }\n\n accounts(): readonly Account[] {\n return this.#state().accounts.map(({ needs, ...account }) => {\n const shown = this.#signing.has(account.id)\n ? { ...account, status: 'signing-in' as const }\n : account;\n return needs ? { ...shown, needs } : shown;\n });\n }\n\n async connect(id: string, secret?: string, options: { region?: string } = {}): Promise<void> {\n const account = this.#state().accounts.find((candidate) => candidate.id === id);\n const login = account?.auth === 'oauth' ? this.#login : null;\n const key = secret ?? (login ? await this.#signIn(id, login) : undefined);\n if (!key)\n throw new Error(`${id} needs ${account?.auth === 'oauth' ? 'a sign-in' : 'an API key'}`);\n await this.#settings.connect(id, key, options.region ?? null, BACKGROUND_CONTEXT);\n }\n\n async #signIn(id: string, login: Login): Promise<string> {\n const controller = new AbortController();\n this.#signing\n .get(id)\n ?.abort(new DOMException('A newer sign-in replaced this one.', 'AbortError'));\n this.#signing.set(id, controller);\n this.emit('accounts', this.accounts());\n try {\n const key = await login(id, () => this.#settings.signIn(id, BACKGROUND_CONTEXT), {\n signal: controller.signal,\n });\n controller.signal.throwIfAborted();\n return key;\n } finally {\n if (this.#signing.get(id) === controller) this.#signing.delete(id);\n this.emit('accounts', this.accounts());\n }\n }\n\n cancel(id: string): void {\n const controller = this.#signing.get(id);\n if (!controller) return;\n this.#signing.delete(id);\n controller.abort(new DOMException('The sign-in was cancelled.', 'AbortError'));\n this.emit('accounts', this.accounts());\n }\n\n disconnect(id: string): void {\n void this.#settings.disconnect(id, BACKGROUND_CONTEXT);\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type {\n Account,\n ModelOption,\n Settings,\n SettingsEvents,\n SettingsPort,\n} from '@ai-ecoverse/slicc-spectrum/ui';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AgentSettings, SettingsState, SignIn } from '../services.ts';\nimport type { AgentAdapter } from './agent-port.ts';\nimport { Emitter } from './emitter.ts';\nimport { mcpLogin } from './mcp-login.ts';\n\nexport const SETTINGS_KEY = 'slicc-agent.settings';\n\nconst defaults: Settings = {\n color: 'system',\n model: '',\n thinking: 'off',\n sendOnEnter: true,\n showThinking: false,\n diffStyle: 'unified',\n};\n\ntype Local = Omit<Settings, 'model' | 'thinking'>;\n\nexport type Login = (\n providerId: string,\n signIn: () => Promise<SignIn | null>,\n options: { signal: AbortSignal }\n) => Promise<string>;\n\nexport class SettingsAdapter extends Emitter<SettingsEvents> implements SettingsPort {\n readonly #settings: AgentSettings;\n readonly #agent: AgentAdapter;\n readonly #storage: Pick<Storage, 'getItem' | 'setItem'> | null;\n readonly #login: Login | null;\n readonly #mcpLogin: Login;\n readonly #signing = new Map<string, AbortController>();\n #local: Local;\n\n constructor(\n settings: AgentSettings,\n agent: AgentAdapter,\n storage: Pick<Storage, 'getItem' | 'setItem'> | null,\n login: Login | null = null,\n mcp: Login = mcpLogin() as Login\n ) {\n super();\n this.#mcpLogin = mcp;\n this.#settings = settings;\n this.#agent = agent;\n this.#storage = storage;\n this.#login = login;\n const saved = JSON.parse(storage?.getItem(SETTINGS_KEY) ?? '{}') as Partial<Local>;\n const { model: _model, thinking: _thinking, ...local } = { ...defaults, ...saved };\n this.#local = local;\n settings.state.subscribe(() => {\n this.emit('accounts', this.accounts());\n this.emit('settings', this.get());\n });\n agent.on('agents', () => this.emit('settings', this.get()));\n }\n\n #state(): SettingsState {\n return this.#settings.state.value ?? { models: [], accounts: [] };\n }\n\n get(): Settings {\n const cone = this.#agent.list()[0];\n return { ...this.#local, model: cone?.model ?? '', thinking: cone?.thinking ?? 'off' };\n }\n\n update(patch: Partial<Settings>): void {\n const { model, thinking, ...local } = patch;\n const id = this.#agent.active();\n if (model !== undefined) this.#agent.setModel(id, model);\n if (thinking !== undefined) this.#agent.setThinking(id, thinking);\n this.#local = { ...this.#local, ...local };\n this.#storage?.setItem(SETTINGS_KEY, JSON.stringify(this.#local));\n this.emit('settings', this.get());\n }\n\n models(): readonly ModelOption[] {\n return this.#state().models.map(({ id, label, provider, kind, reasoning, images }) => ({\n id,\n label,\n provider,\n kind,\n reasoning,\n ...(images === undefined ? {} : { images }),\n }));\n }\n\n accounts(): readonly Account[] {\n return this.#state().accounts.map(({ needs, ...account }) => {\n const shown = this.#signing.has(account.id)\n ? { ...account, status: 'signing-in' as const }\n : account;\n return needs ? { ...shown, needs } : shown;\n });\n }\n\n async connect(id: string, secret?: string, options: { region?: string } = {}): Promise<void> {\n const account = this.#state().accounts.find((candidate) => candidate.id === id);\n const login =\n account?.auth !== 'oauth' ? null : account.group === 'mcp' ? this.#mcpLogin : this.#login;\n const key = secret ?? (login ? await this.#signIn(id, login) : undefined);\n if (!key)\n throw new Error(`${id} needs ${account?.auth === 'oauth' ? 'a sign-in' : 'an API key'}`);\n await this.#settings.connect(id, key, options.region ?? null, BACKGROUND_CONTEXT);\n }\n\n async #signIn(id: string, login: Login): Promise<string> {\n const controller = new AbortController();\n this.#signing\n .get(id)\n ?.abort(new DOMException('A newer sign-in replaced this one.', 'AbortError'));\n this.#signing.set(id, controller);\n this.emit('accounts', this.accounts());\n try {\n const key = await login(\n id,\n () => this.#settings.signIn(id, BACKGROUND_CONTEXT) as Promise<SignIn | null>,\n {\n signal: controller.signal,\n }\n );\n controller.signal.throwIfAborted();\n return key;\n } finally {\n if (this.#signing.get(id) === controller) this.#signing.delete(id);\n this.emit('accounts', this.accounts());\n }\n }\n\n cancel(id: string): void {\n const controller = this.#signing.get(id);\n if (!controller) return;\n this.#signing.delete(id);\n controller.abort(new DOMException('The sign-in was cancelled.', 'AbortError'));\n this.emit('accounts', this.accounts());\n }\n\n disconnect(id: string): void {\n void this.#settings.disconnect(id, BACKGROUND_CONTEXT);\n }\n}\n"],
5
+ "mappings": "AAOA,SAAS,0BAA0B;AAGnC,SAAS,eAAe;AACxB,SAAS,gBAAgB;AAElB,MAAM,eAAe;AAE5B,MAAM,WAAqB;AAAA,EACzB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,aAAa;AAAA,EACb,cAAc;AAAA,EACd,WAAW;AACb;AAUO,MAAM,wBAAwB,QAAgD;AAAA,EAC1E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW,oBAAI,IAA6B;AAAA,EACrD;AAAA,EAEA,YACE,UACA,OACA,SACA,QAAsB,MACtB,MAAa,SAAS,GACtB;AACA,UAAM;AACN,SAAK,YAAY;AACjB,SAAK,YAAY;AACjB,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,SAAS;AACd,UAAM,QAAQ,KAAK,MAAM,SAAS,QAAQ,YAAY,KAAK,IAAI;AAC/D,UAAM,EAAE,OAAO,QAAQ,UAAU,WAAW,GAAG,MAAM,IAAI,EAAE,GAAG,UAAU,GAAG,MAAM;AACjF,SAAK,SAAS;AACd,aAAS,MAAM,UAAU,MAAM;AAC7B,WAAK,KAAK,YAAY,KAAK,SAAS,CAAC;AACrC,WAAK,KAAK,YAAY,KAAK,IAAI,CAAC;AAAA,IAClC,CAAC;AACD,UAAM,GAAG,UAAU,MAAM,KAAK,KAAK,YAAY,KAAK,IAAI,CAAC,CAAC;AAAA,EAC5D;AAAA,EAEA,SAAwB;AACtB,WAAO,KAAK,UAAU,MAAM,SAAS,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,EAAE;AAAA,EAClE;AAAA,EAEA,MAAgB;AACd,UAAM,OAAO,KAAK,OAAO,KAAK,EAAE,CAAC;AACjC,WAAO,EAAE,GAAG,KAAK,QAAQ,OAAO,MAAM,SAAS,IAAI,UAAU,MAAM,YAAY,MAAM;AAAA,EACvF;AAAA,EAEA,OAAO,OAAgC;AACrC,UAAM,EAAE,OAAO,UAAU,GAAG,MAAM,IAAI;AACtC,UAAM,KAAK,KAAK,OAAO,OAAO;AAC9B,QAAI,UAAU,OAAW,MAAK,OAAO,SAAS,IAAI,KAAK;AACvD,QAAI,aAAa,OAAW,MAAK,OAAO,YAAY,IAAI,QAAQ;AAChE,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,MAAM;AACzC,SAAK,UAAU,QAAQ,cAAc,KAAK,UAAU,KAAK,MAAM,CAAC;AAChE,SAAK,KAAK,YAAY,KAAK,IAAI,CAAC;AAAA,EAClC;AAAA,EAEA,SAAiC;AAC/B,WAAO,KAAK,OAAO,EAAE,OAAO,IAAI,CAAC,EAAE,IAAI,OAAO,UAAU,MAAM,WAAW,OAAO,OAAO;AAAA,MACrF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAI,WAAW,SAAY,CAAC,IAAI,EAAE,OAAO;AAAA,IAC3C,EAAE;AAAA,EACJ;AAAA,EAEA,WAA+B;AAC7B,WAAO,KAAK,OAAO,EAAE,SAAS,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,MAAM;AAC3D,YAAM,QAAQ,KAAK,SAAS,IAAI,QAAQ,EAAE,IACtC,EAAE,GAAG,SAAS,QAAQ,aAAsB,IAC5C;AACJ,aAAO,QAAQ,EAAE,GAAG,OAAO,MAAM,IAAI;AAAA,IACvC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ,IAAY,QAAiB,UAA+B,CAAC,GAAkB;AAC3F,UAAM,UAAU,KAAK,OAAO,EAAE,SAAS,KAAK,CAAC,cAAc,UAAU,OAAO,EAAE;AAC9E,UAAM,QACJ,SAAS,SAAS,UAAU,OAAO,QAAQ,UAAU,QAAQ,KAAK,YAAY,KAAK;AACrF,UAAM,MAAM,WAAW,QAAQ,MAAM,KAAK,QAAQ,IAAI,KAAK,IAAI;AAC/D,QAAI,CAAC;AACH,YAAM,IAAI,MAAM,GAAG,EAAE,UAAU,SAAS,SAAS,UAAU,cAAc,YAAY,EAAE;AACzF,UAAM,KAAK,UAAU,QAAQ,IAAI,KAAK,QAAQ,UAAU,MAAM,kBAAkB;AAAA,EAClF;AAAA,EAEA,MAAM,QAAQ,IAAY,OAA+B;AACvD,UAAM,aAAa,IAAI,gBAAgB;AACvC,SAAK,SACF,IAAI,EAAE,GACL,MAAM,IAAI,aAAa,sCAAsC,YAAY,CAAC;AAC9E,SAAK,SAAS,IAAI,IAAI,UAAU;AAChC,SAAK,KAAK,YAAY,KAAK,SAAS,CAAC;AACrC,QAAI;AACF,YAAM,MAAM,MAAM;AAAA,QAChB;AAAA,QACA,MAAM,KAAK,UAAU,OAAO,IAAI,kBAAkB;AAAA,QAClD;AAAA,UACE,QAAQ,WAAW;AAAA,QACrB;AAAA,MACF;AACA,iBAAW,OAAO,eAAe;AACjC,aAAO;AAAA,IACT,UAAE;AACA,UAAI,KAAK,SAAS,IAAI,EAAE,MAAM,WAAY,MAAK,SAAS,OAAO,EAAE;AACjE,WAAK,KAAK,YAAY,KAAK,SAAS,CAAC;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,OAAO,IAAkB;AACvB,UAAM,aAAa,KAAK,SAAS,IAAI,EAAE;AACvC,QAAI,CAAC,WAAY;AACjB,SAAK,SAAS,OAAO,EAAE;AACvB,eAAW,MAAM,IAAI,aAAa,8BAA8B,YAAY,CAAC;AAC7E,SAAK,KAAK,YAAY,KAAK,SAAS,CAAC;AAAA,EACvC;AAAA,EAEA,WAAW,IAAkB;AAC3B,SAAK,KAAK,UAAU,WAAW,IAAI,kBAAkB;AAAA,EACvD;AACF;",
6
6
  "names": []
7
7
  }
@@ -26,8 +26,10 @@ export interface AgentWorkerOptions {
26
26
  codemodeWorker?: string | URL;
27
27
  catalog?: string;
28
28
  users?: boolean;
29
+ mcpRedirect?: string;
29
30
  }
30
31
  export declare function usersOf(admin: MessagePort | undefined, wanted: boolean | undefined, attach: KernelAttach): UserClients | undefined;
31
32
  export declare const STORE_DIR = "/.slicc";
33
+ export declare const MCP_CALLBACK = "/oauth/mcp-callback";
32
34
  export declare function alive(client: KernelClient): (pid: number) => Promise<boolean>;
33
35
  export declare function runAgentWorker(scope: AgentWorkerScope, options?: AgentWorkerOptions): Promise<AgentHost>;
@@ -0,0 +1,20 @@
1
+ import type { CredentialStore } from '@earendil-works/pi-ai';
2
+ import type { McpFetch } from '@earendil-works/pi-mcp';
3
+ import type { AccountState } from '../services.ts';
4
+ import type { McpCatalog } from './catalog.ts';
5
+ import type { McpServerEntry } from './config.ts';
6
+ import type { McpServerConnection } from './connection.ts';
7
+ import { type McpSignIn, type StoredOAuth } from './oauth.ts';
8
+ export interface McpAccounts {
9
+ accounts(): Promise<AccountState[]>;
10
+ signIn(id: string): Promise<McpSignIn>;
11
+ connect(id: string, secret: string): Promise<void>;
12
+ disconnect(id: string): Promise<void>;
13
+ onChange(listener: () => void): () => void;
14
+ }
15
+ export interface AccountSources {
16
+ credentials: CredentialStore;
17
+ oauth: (entry: McpServerEntry) => StoredOAuth;
18
+ fetch: McpFetch;
19
+ }
20
+ export declare function mcpAccounts(catalog: McpCatalog, sources: AccountSources, restart: (connection: McpServerConnection) => void, listeners: Set<() => void>): McpAccounts;
@@ -7,6 +7,13 @@ export interface McpServerConfig {
7
7
  enabled?: boolean;
8
8
  timeout?: number;
9
9
  description?: string;
10
+ oauth?: McpOAuthConfig;
11
+ }
12
+ export interface McpOAuthConfig {
13
+ clientId?: string;
14
+ clientName?: string;
15
+ scope?: string;
16
+ authServerMetadataUrl?: string;
10
17
  }
11
18
  export interface McpServerEntry {
12
19
  name: string;
@@ -25,6 +32,7 @@ export declare const DEFAULT_TIMEOUT_SECONDS = 60;
25
32
  export declare function placeholders(value: string): string[];
26
33
  export declare function literalSecret(header: string, value: string): boolean;
27
34
  type Checked = McpServerConfig | McpConfigProblem;
35
+ export declare function usesOAuth(config: McpServerConfig): boolean;
28
36
  export declare function validateServer(name: string, raw: unknown): Checked;
29
37
  export declare function parseMcpConfig(text: string | undefined): McpConfig;
30
38
  export declare function nameMatcher(entries: readonly string[]): (name: string) => boolean;
@@ -9,6 +9,7 @@ export type TransportFactory = (entry: McpServerEntry) => Promise<{
9
9
  }>;
10
10
  export declare const CONNECT_RETRY_DELAYS_MS: number[];
11
11
  export declare const CLIENT_NAME = "slicc";
12
+ export declare function signInNeeded(error: unknown): boolean;
12
13
  export declare function errorMessage(error: unknown): string;
13
14
  export declare function isTransient(error: unknown): boolean;
14
15
  export declare function isMcpApp(item: {
@@ -0,0 +1,58 @@
1
+ import type { CredentialStore } from '@earendil-works/pi-ai';
2
+ import type { AuthProvider, McpFetch } from '@earendil-works/pi-mcp';
3
+ import { authorizeMcp, McpOAuthAuthorizationRequiredError, type McpOAuthState } from '@earendil-works/pi-mcp/oauth';
4
+ import type { McpSignIn } from '../services.ts';
5
+ import type { McpServerConfig } from './config.ts';
6
+ type Provider = Parameters<typeof authorizeMcp>[0];
7
+ type Tokens = Awaited<ReturnType<Provider['tokens']>>;
8
+ type Client = Awaited<ReturnType<Provider['clientInformation']>>;
9
+ type Discovery = NonNullable<McpOAuthState['discovery']>;
10
+ export declare const CLIENT_NAME = "SLICC";
11
+ export declare function sentence(text: string): string;
12
+ export declare function secureUrl(url: URL): boolean;
13
+ export declare function signInRequired(cause?: unknown): McpOAuthAuthorizationRequiredError;
14
+ export declare function authProvider(provider: StoredOAuth, server: OAuthServer): AuthProvider;
15
+ export declare function pkce(): Promise<{
16
+ verifier: string;
17
+ challenge: string;
18
+ }>;
19
+ export declare function randomState(): string;
20
+ export interface OAuthServer {
21
+ name: string;
22
+ config: McpServerConfig;
23
+ }
24
+ export declare class StoredOAuth implements Provider {
25
+ #private;
26
+ readonly redirectUrl: string;
27
+ readonly clientMetadata: Provider['clientMetadata'];
28
+ constructor(server: OAuthServer, credentials: CredentialStore, redirectUrl: string);
29
+ get serverUrl(): string;
30
+ load(): Promise<McpOAuthState>;
31
+ update(change: (state: McpOAuthState) => McpOAuthState): Promise<void>;
32
+ signedIn(): Promise<boolean>;
33
+ state(): string;
34
+ clientInformation(): Promise<Client>;
35
+ saveClientInformation(information: NonNullable<Client>): Promise<void>;
36
+ tokens(): Promise<Tokens>;
37
+ saveTokens(tokens: NonNullable<Tokens>): Promise<void>;
38
+ redirectToAuthorization(): void;
39
+ saveCodeVerifier(verifier: string): Promise<void>;
40
+ codeVerifier(): Promise<string>;
41
+ invalidateCredentials(kind: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): Promise<void>;
42
+ saveDiscoveryState(discovery: Discovery): Promise<void>;
43
+ discoveryState(): Promise<Discovery | undefined>;
44
+ forget(): Promise<void>;
45
+ }
46
+ export type { McpSignIn } from '../services.ts';
47
+ export declare function beginSignIn(provider: StoredOAuth, server: OAuthServer, options: {
48
+ fetch: McpFetch;
49
+ resourceMetadataUrl?: URL;
50
+ }): Promise<McpSignIn>;
51
+ export interface Callback {
52
+ code?: string;
53
+ state?: string;
54
+ iss?: string;
55
+ error?: string;
56
+ error_description?: string;
57
+ }
58
+ export declare function finishSignIn(provider: StoredOAuth, server: OAuthServer, answer: string, fetch: McpFetch): Promise<void>;
@@ -3,7 +3,7 @@ import type { CredentialStore } from '@earendil-works/pi-ai';
3
3
  import type { Registry } from '@earendil-works/pi-durable';
4
4
  import type { ExecutionEnv } from '@earendil-works/pi-durable/env';
5
5
  import type { LickEvent, Licks } from '../licks/licks.ts';
6
- import type { AccountState } from '../services.ts';
6
+ import { type McpAccounts } from './accounts.ts';
7
7
  import { McpCatalog } from './catalog.ts';
8
8
  import { type McpConfigProblem } from './config.ts';
9
9
  import { McpServerConnection } from './connection.ts';
@@ -20,6 +20,7 @@ export interface McpAttach {
20
20
  reloadMs: number;
21
21
  codemode: boolean;
22
22
  cors: boolean;
23
+ redirect: string;
23
24
  }
24
25
  export interface ServerStatus {
25
26
  name: string;
@@ -27,12 +28,7 @@ export interface ServerStatus {
27
28
  tools: number;
28
29
  error?: string;
29
30
  }
30
- export interface McpAccounts {
31
- accounts(): Promise<AccountState[]>;
32
- connect(id: string, secret: string): Promise<void>;
33
- disconnect(id: string): Promise<void>;
34
- onChange(listener: () => void): () => void;
35
- }
31
+ export type { McpAccounts } from './accounts.ts';
36
32
  export interface McpRuntime extends McpAccounts {
37
33
  status(): ServerStatus[];
38
34
  reload(context: Context): Promise<void>;
@@ -42,7 +38,6 @@ export declare function problemLick(problem: McpConfigProblem, home: string): Li
42
38
  export declare function stateLick(connection: McpServerConnection, cors: boolean): LickEvent | undefined;
43
39
  export declare function connectedLick(connection: McpServerConnection): LickEvent;
44
40
  export declare function logLine(server: string, params: unknown, secrets: readonly string[], at: Date): string;
45
- export declare function mcpAccounts(catalog: McpCatalog, credentials: CredentialStore, restart: (connection: McpServerConnection) => void, listeners: Set<() => void>): McpAccounts;
46
41
  export declare function attachMcp(catalog: McpCatalog, options: McpAttach, context: Context): Promise<McpRuntime>;
47
42
  export declare function startMcp(registry: Registry, options: McpAttach, context: Context): Promise<{
48
43
  catalog: McpCatalog;
@@ -194,6 +194,10 @@ export interface SignIn {
194
194
  scopes: string;
195
195
  imsEnvironment: string;
196
196
  }
197
+ export interface McpSignIn {
198
+ url: string;
199
+ state: string;
200
+ }
197
201
  export interface SettingsState {
198
202
  models: ModelChoice[];
199
203
  accounts: AccountState[];
@@ -203,6 +207,6 @@ export interface AgentSettings {
203
207
  readonly state: ReplicatedState<SettingsState>;
204
208
  connect(providerId: string, secret: string, region: string | null, context: Context): Promise<void>;
205
209
  disconnect(providerId: string, context: Context): Promise<void>;
206
- signIn(providerId: string, context: Context): Promise<SignIn | null>;
210
+ signIn(providerId: string, context: Context): Promise<SignIn | McpSignIn | null>;
207
211
  }
208
212
  export declare const AgentSettings: import("@earendil-works/chord").Service<AgentSettings>;
@@ -12,6 +12,7 @@ export declare const DEFAULT_REGIONS: Readonly<Record<string, string>>;
12
12
  export declare function sliccProviders(): Provider[];
13
13
  export declare function withDefaultRegions(credentials: CredentialStore): CredentialStore;
14
14
  export declare function createSliccModels(credentials: CredentialStore, providers?: Provider<import("@earendil-works/pi-ai").Api>[]): MutableModels;
15
+ export declare function shown<T>(run: () => Promise<T>): Promise<T>;
15
16
  export type UsageReader = (token: string) => Promise<Budget | null>;
16
17
  export declare function createAgentSettings(models: MutableModels, credentials: CredentialStore, providers?: Provider<import("@earendil-works/pi-ai").Api>[], options?: {
17
18
  usage?: UsageReader;