@assertrx/dino 1.0.1 → 1.0.3

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,34 @@
1
+ - generic [ref=f10e2]:
2
+ - navigation [ref=f10e3]:
3
+ - generic [ref=f10e8]:
4
+ - button "Google apps" [ref=f10e12] [cursor=pointer]
5
+ - link "Sign in" [ref=f10e16] [cursor=pointer]:
6
+ - /url: https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://www.google.com/imghp%3Fhl%3Den&ec=GAZAAg
7
+ - generic [ref=f10e18]:
8
+ - img "Google Images" [ref=f10e19]
9
+ - generic [ref=f10e20]: Images
10
+ - search [ref=f10e22]:
11
+ - generic [ref=f10e24]:
12
+ - generic [ref=f10e26]:
13
+ - combobox "Search" [active] [ref=f10e33]
14
+ - generic [ref=f10e35]:
15
+ - button "Search by voice" [ref=f10e36] [cursor=pointer]
16
+ - button "Search by image" [ref=f10e39] [cursor=pointer]
17
+ - button "Google Search" [ref=f10e44] [cursor=pointer]
18
+ - contentinfo [ref=f10e47]:
19
+ - generic [ref=f10e48]:
20
+ - generic [ref=f10e49]:
21
+ - link "About" [ref=f10e50] [cursor=pointer]:
22
+ - /url: https://about.google/?utm_source=google-US&utm_medium=referral&utm_campaign=hp-footer&fg=1
23
+ - link "Advertising" [ref=f10e51] [cursor=pointer]:
24
+ - /url: https://www.google.com/intl/en_us/ads/?subid=ww-ww-et-g-awa-a-g_hpafoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpafooter&fg=1
25
+ - link "Business" [ref=f10e52] [cursor=pointer]:
26
+ - /url: https://www.google.com/services/?subid=ww-ww-et-g-awa-a-g_hpbfoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpbfooter&fg=1
27
+ - link "How Search works" [ref=f10e53] [cursor=pointer]:
28
+ - /url: https://google.com/search/howsearchworks/?fg=1
29
+ - generic [ref=f10e54]:
30
+ - link "Privacy" [ref=f10e55] [cursor=pointer]:
31
+ - /url: https://policies.google.com/privacy?hl=en&fg=1
32
+ - link "Terms" [ref=f10e56] [cursor=pointer]:
33
+ - /url: https://policies.google.com/terms?hl=en&fg=1
34
+ - button "Settings" [ref=f10e60] [cursor=pointer]
@@ -0,0 +1,52 @@
1
+ - generic [ref=f11e2]:
2
+ - navigation [ref=f11e3]:
3
+ - link "About" [ref=f11e4] [cursor=pointer]:
4
+ - /url: https://about.google/?fg=1&utm_source=google-US&utm_medium=referral&utm_campaign=hp-header
5
+ - link "Store" [ref=f11e5] [cursor=pointer]:
6
+ - /url: https://store.google.com/US?utm_source=hp_header&utm_medium=google_ooo&utm_campaign=GS100042&hl=en-US
7
+ - generic [ref=f11e7]:
8
+ - generic [ref=f11e8]:
9
+ - link "Gmail" [ref=f11e10] [cursor=pointer]:
10
+ - /url: https://mail.google.com/mail/&ogbl
11
+ - link "Search for Images" [ref=f11e12] [cursor=pointer]:
12
+ - /url: https://www.google.com/imghp?hl=en&ogbl
13
+ - text: Images
14
+ - button "Google apps" [ref=f11e15] [cursor=pointer]
15
+ - link "Sign in" [ref=f11e20] [cursor=pointer]:
16
+ - /url: https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://www.google.com/&ec=futura_exp_og_so_72776762_e
17
+ - generic [ref=f11e22]:
18
+ - link [ref=f11e24] [cursor=pointer]:
19
+ - /url: /search?sca_esv=b3cb815f54d8ec19&q=Which+National+Parks+have+some+of+the+most+scenic+overlooks%3F&udm=50&oi=ddle&noiga=1&ct=460148972&peek_pws=0&aep=84&sa=X&ved=0ahUKEwjx9MrgtsGWAxWpEVkFHdh1AvEQPQgH
20
+ - img "Celebrating National Park Week at Yosemite" [ref=f11e26]
21
+ - button "Share" [ref=f11e28]
22
+ - search [ref=f11e36]:
23
+ - generic [ref=f11e38]:
24
+ - generic [ref=f11e40]:
25
+ - button "Add files and tools" [ref=f11e45] [cursor=pointer]
26
+ - combobox "Search" [active] [ref=f11e50]
27
+ - generic [ref=f11e51]:
28
+ - generic [ref=f11e52]:
29
+ - button "Search by voice" [ref=f11e55] [cursor=pointer]
30
+ - button "Search by image" [ref=f11e60] [cursor=pointer]
31
+ - link "AI Mode" [ref=f11e63] [cursor=pointer]
32
+ - generic [ref=f11e76]:
33
+ - button "Google Search" [ref=f11e77] [cursor=pointer]
34
+ - button "I'm Feeling Lucky" [ref=f11e78] [cursor=pointer]
35
+ - 'link "Celebrate National Park Week: Explore Yosemite with Google 🌲" [ref=f11e88] [cursor=pointer]'
36
+ - contentinfo [ref=f11e90]:
37
+ - generic [ref=f11e91]:
38
+ - generic [ref=f11e92]:
39
+ - link "Advertising" [ref=f11e93] [cursor=pointer]:
40
+ - /url: https://www.google.com/intl/en_us/ads/?subid=ww-ww-et-g-awa-a-g_hpafoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpafooter&fg=1
41
+ - link "Business" [ref=f11e94] [cursor=pointer]:
42
+ - /url: https://www.google.com/services/?subid=ww-ww-et-g-awa-a-g_hpbfoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpbfooter&fg=1
43
+ - link "How Search works" [ref=f11e95] [cursor=pointer]:
44
+ - /url: https://google.com/search/howsearchworks/?fg=1
45
+ - link "Build, create, and do more with AI tools from Google" [ref=f11e97] [cursor=pointer]:
46
+ - /url: https://ai.google/helpful-tools/?utm_source=googlehpfooter&utm_medium=housepromos&utm_campaign=bottom-footer
47
+ - generic [ref=f11e98]:
48
+ - link "Privacy" [ref=f11e99] [cursor=pointer]:
49
+ - /url: https://policies.google.com/privacy?hl=en&fg=1
50
+ - link "Terms" [ref=f11e100] [cursor=pointer]:
51
+ - /url: https://policies.google.com/terms?hl=en&fg=1
52
+ - button "Settings" [ref=f11e104] [cursor=pointer]
@@ -0,0 +1,203 @@
1
+ - generic [ref=f11e2]:
2
+ - navigation [ref=f11e3]:
3
+ - link "About" [ref=f11e4] [cursor=pointer]:
4
+ - /url: https://about.google/?fg=1&utm_source=google-US&utm_medium=referral&utm_campaign=hp-header
5
+ - link "Store" [ref=f11e5] [cursor=pointer]:
6
+ - /url: https://store.google.com/US?utm_source=hp_header&utm_medium=google_ooo&utm_campaign=GS100042&hl=en-US
7
+ - generic [ref=f11e7]:
8
+ - generic [ref=f11e8]:
9
+ - link "Gmail" [ref=f11e10] [cursor=pointer]:
10
+ - /url: https://mail.google.com/mail/&ogbl
11
+ - link "Search for Images" [ref=f11e12] [cursor=pointer]:
12
+ - /url: https://www.google.com/imghp?hl=en&ogbl
13
+ - text: Images
14
+ - button "Google apps" [expanded] [ref=f11e15] [cursor=pointer]
15
+ - generic [ref=f11e19]:
16
+ - link "Sign in" [ref=f11e20] [cursor=pointer]:
17
+ - /url: https://accounts.google.com/ServiceLogin?hl=en&passive=true&continue=https://www.google.com/&ec=futura_exp_og_so_72776762_e
18
+ - iframe [active] [ref=f11e107]:
19
+ - complementary [ref=f12e7]:
20
+ - generic "Google apps" [ref=f12e8]:
21
+ - generic [ref=f12e9]:
22
+ - generic [ref=f12e10]:
23
+ - link "Sign in" [active] [ref=f12e11] [cursor=pointer]:
24
+ - /url: https://accounts.google.com/ServiceLogin?continue=https://www.google.com/?zx=1787854964982
25
+ - text: to arrange apps
26
+ - generic [ref=f12e12]:
27
+ - list [ref=f12e15]:
28
+ - listitem [ref=f12e16]:
29
+ - link "Account, row 1 of 5 and column 1 of 3 in the first section" [ref=f12e17] [cursor=pointer]:
30
+ - /url: https://myaccount.google.com/?utm_source=OGB&utm_medium=app
31
+ - generic [ref=f12e20]: Account
32
+ - listitem [ref=f12e21]:
33
+ - link "Search, row 1 of 5 and column 2 of 3 in the first section" [ref=f12e22] [cursor=pointer]:
34
+ - /url: https://www.google.com/
35
+ - generic [ref=f12e25]: Search
36
+ - listitem [ref=f12e26]:
37
+ - link "Maps, row 1 of 5 and column 3 of 3 in the first section" [ref=f12e27] [cursor=pointer]:
38
+ - /url: https://maps.google.com/
39
+ - generic [ref=f12e30]: Maps
40
+ - listitem [ref=f12e31]:
41
+ - link "YouTube, row 2 of 5 and column 1 of 3 in the first section" [ref=f12e32] [cursor=pointer]:
42
+ - /url: https://www.youtube.com
43
+ - generic [ref=f12e35]: YouTube
44
+ - listitem [ref=f12e36]:
45
+ - link "News, row 2 of 5 and column 2 of 3 in the first section" [ref=f12e37] [cursor=pointer]:
46
+ - /url: https://news.google.com
47
+ - generic [ref=f12e40]: News
48
+ - listitem [ref=f12e41]:
49
+ - link "Gmail, row 2 of 5 and column 3 of 3 in the first section" [ref=f12e42] [cursor=pointer]:
50
+ - /url: https://mail.google.com/mail/
51
+ - generic [ref=f12e45]: Gmail
52
+ - listitem [ref=f12e46]:
53
+ - link "Meet, row 3 of 5 and column 1 of 3 in the first section" [ref=f12e47] [cursor=pointer]:
54
+ - /url: https://meet.google.com?hs=197
55
+ - generic [ref=f12e50]: Meet
56
+ - listitem [ref=f12e51]:
57
+ - link "Chat, row 3 of 5 and column 2 of 3 in the first section" [ref=f12e52] [cursor=pointer]:
58
+ - /url: https://chat.google.com
59
+ - generic [ref=f12e55]: Chat
60
+ - listitem [ref=f12e56]:
61
+ - link "Contacts, row 3 of 5 and column 3 of 3 in the first section" [ref=f12e57] [cursor=pointer]:
62
+ - /url: https://contacts.google.com/
63
+ - generic [ref=f12e60]: Contacts
64
+ - listitem [ref=f12e61]:
65
+ - link "Drive, row 4 of 5 and column 1 of 3 in the first section" [ref=f12e62] [cursor=pointer]:
66
+ - /url: https://drive.google.com/
67
+ - generic [ref=f12e65]: Drive
68
+ - listitem [ref=f12e66]:
69
+ - link "Calendar, row 4 of 5 and column 2 of 3 in the first section" [ref=f12e67] [cursor=pointer]:
70
+ - /url: https://calendar.google.com/calendar
71
+ - generic [ref=f12e70]: Calendar
72
+ - listitem [ref=f12e71]:
73
+ - link "Play, row 4 of 5 and column 3 of 3 in the first section" [ref=f12e72] [cursor=pointer]:
74
+ - /url: https://play.google.com/
75
+ - generic [ref=f12e75]: Play
76
+ - listitem [ref=f12e76]:
77
+ - link "Translate, row 5 of 5 and column 1 of 3 in the first section" [ref=f12e77] [cursor=pointer]:
78
+ - /url: https://translate.google.com/
79
+ - generic [ref=f12e80]: Translate
80
+ - listitem [ref=f12e81]:
81
+ - link "Photos, row 5 of 5 and column 2 of 3 in the first section" [ref=f12e82] [cursor=pointer]:
82
+ - /url: https://photos.google.com/
83
+ - generic [ref=f12e85]: Photos
84
+ - listitem [ref=f12e86]:
85
+ - link "Shopping, row 5 of 5 and column 3 of 3 in the first section" [ref=f12e87] [cursor=pointer]:
86
+ - /url: https://www.google.com/shopping?source=og
87
+ - generic [ref=f12e90]: Shopping
88
+ - list [ref=f12e93]:
89
+ - listitem [ref=f12e94]:
90
+ - link "Finance, row 1 of 7 and column 1 of 3 in the second section" [ref=f12e95] [cursor=pointer]:
91
+ - /url: https://www.google.com/finance
92
+ - generic [ref=f12e98]: Finance
93
+ - listitem [ref=f12e99]:
94
+ - link "Docs, row 1 of 7 and column 2 of 3 in the second section" [ref=f12e100] [cursor=pointer]:
95
+ - /url: https://docs.google.com/document/?usp=docs_alc
96
+ - generic [ref=f12e103]: Docs
97
+ - listitem [ref=f12e104]:
98
+ - link "Sheets, row 1 of 7 and column 3 of 3 in the second section" [ref=f12e105] [cursor=pointer]:
99
+ - /url: https://docs.google.com/spreadsheets/?usp=sheets_alc
100
+ - generic [ref=f12e108]: Sheets
101
+ - listitem [ref=f12e109]:
102
+ - link "Slides, row 2 of 7 and column 1 of 3 in the second section" [ref=f12e110] [cursor=pointer]:
103
+ - /url: https://docs.google.com/presentation/?usp=slides_alc
104
+ - generic [ref=f12e113]: Slides
105
+ - listitem [ref=f12e114]:
106
+ - link "Books, row 2 of 7 and column 2 of 3 in the second section" [ref=f12e115] [cursor=pointer]:
107
+ - /url: https://books.google.com/
108
+ - generic [ref=f12e118]: Books
109
+ - listitem [ref=f12e119]:
110
+ - link "Blogger, row 2 of 7 and column 3 of 3 in the second section" [ref=f12e120] [cursor=pointer]:
111
+ - /url: https://www.blogger.com/
112
+ - generic [ref=f12e123]: Blogger
113
+ - listitem [ref=f12e124]:
114
+ - link "Keep, row 3 of 7 and column 1 of 3 in the second section" [ref=f12e125] [cursor=pointer]:
115
+ - /url: https://keep.google.com
116
+ - generic [ref=f12e128]: Keep
117
+ - listitem [ref=f12e129]:
118
+ - link "Earth, row 3 of 7 and column 2 of 3 in the second section" [ref=f12e130] [cursor=pointer]:
119
+ - /url: https://earth.google.com/web/
120
+ - generic [ref=f12e133]: Earth
121
+ - listitem [ref=f12e134]:
122
+ - link "Saved, row 3 of 7 and column 3 of 3 in the second section" [ref=f12e135] [cursor=pointer]:
123
+ - /url: https://www.google.com/save
124
+ - generic [ref=f12e138]: Saved
125
+ - listitem [ref=f12e139]:
126
+ - link "Arts and Culture, row 4 of 7 and column 1 of 3 in the second section" [ref=f12e140] [cursor=pointer]:
127
+ - /url: https://artsandculture.google.com/?utm_source=ogs.google.com&utm_medium=referral
128
+ - generic [ref=f12e143]: Arts and Culture
129
+ - listitem [ref=f12e144]:
130
+ - link "Google Ads, row 4 of 7 and column 2 of 3 in the second section" [ref=f12e145] [cursor=pointer]:
131
+ - /url: https://ads.google.com/ups/routing?subid=ww-ww-xs-ip-awhc-a-ogb_cons!o2&source=203
132
+ - generic [ref=f12e148]: Google Ads
133
+ - listitem [ref=f12e149]:
134
+ - link "Merchant Center, row 4 of 7 and column 3 of 3 in the second section" [ref=f12e150] [cursor=pointer]:
135
+ - /url: https://merchants.google.com
136
+ - generic [ref=f12e153]: Merchant Center
137
+ - listitem [ref=f12e154]:
138
+ - link "Travel, row 5 of 7 and column 1 of 3 in the second section" [ref=f12e155] [cursor=pointer]:
139
+ - /url: https://www.google.com/travel/?dest_src=al
140
+ - generic [ref=f12e158]: Travel
141
+ - listitem [ref=f12e159]:
142
+ - link "Forms, row 5 of 7 and column 2 of 3 in the second section" [ref=f12e160] [cursor=pointer]:
143
+ - /url: https://docs.google.com/forms/
144
+ - generic [ref=f12e163]: Forms
145
+ - listitem [ref=f12e164]:
146
+ - link "Store, row 5 of 7 and column 3 of 3 in the second section" [ref=f12e165] [cursor=pointer]:
147
+ - /url: https://store.google.com?utm_source=app_launcher&utm_medium=google_oo&utm_campaign=GS107345
148
+ - generic [ref=f12e168]: Store
149
+ - listitem [ref=f12e169]:
150
+ - link "Chrome Web Store, row 6 of 7 and column 1 of 3 in the second section" [ref=f12e170] [cursor=pointer]:
151
+ - /url: https://chrome.google.com/webstore?utm_source=app-launcher
152
+ - generic [ref=f12e173]: Chrome Web Store
153
+ - listitem [ref=f12e174]:
154
+ - link "Google Fi Wireless, row 6 of 7 and column 2 of 3 in the second section" [ref=f12e175] [cursor=pointer]:
155
+ - /url: https://fi.google.com/?utm_source=app_launcher&utm_medium=embedded_promo
156
+ - generic [ref=f12e178]: Google Fi Wireless
157
+ - listitem [ref=f12e179]:
158
+ - link "Google Analytics, row 6 of 7 and column 3 of 3 in the second section" [ref=f12e180] [cursor=pointer]:
159
+ - /url: https://analytics.google.com/analytics/web?utm_source=OGB&utm_medium=app
160
+ - generic [ref=f12e183]: Google Analytics
161
+ - listitem [ref=f12e184]:
162
+ - link "YouTube Music, row 7 of 7 and column 1 of 3 in the second section" [ref=f12e185] [cursor=pointer]:
163
+ - /url: https://music.youtube.com?utm_source=app_launcher
164
+ - generic [ref=f12e188]: YouTube Music
165
+ - link "More from Google (opens a new tab)" [ref=f12e190] [cursor=pointer]:
166
+ - /url: https://about.google/products/
167
+ - text: More from Google
168
+ - generic [ref=f11e22]:
169
+ - link [ref=f11e24] [cursor=pointer]:
170
+ - /url: /search?sca_esv=b3cb815f54d8ec19&q=Which+National+Parks+have+some+of+the+most+scenic+overlooks%3F&udm=50&oi=ddle&noiga=1&ct=460148972&peek_pws=0&aep=84&sa=X&ved=0ahUKEwjx9MrgtsGWAxWpEVkFHdh1AvEQPQgH
171
+ - img "Celebrating National Park Week at Yosemite" [ref=f11e26]
172
+ - button "Share" [ref=f11e28]
173
+ - search [ref=f11e36]:
174
+ - generic [ref=f11e38]:
175
+ - generic [ref=f11e40]:
176
+ - button "Add files and tools" [ref=f11e45] [cursor=pointer]
177
+ - combobox "Search" [ref=f11e50]
178
+ - generic [ref=f11e51]:
179
+ - generic [ref=f11e52]:
180
+ - button "Search by voice" [ref=f11e55] [cursor=pointer]
181
+ - button "Search by image" [ref=f11e60] [cursor=pointer]
182
+ - link "AI Mode" [ref=f11e63] [cursor=pointer]
183
+ - generic [ref=f11e76]:
184
+ - button "Google Search" [ref=f11e77] [cursor=pointer]
185
+ - button "I'm Feeling Lucky" [ref=f11e78] [cursor=pointer]
186
+ - 'link "Celebrate National Park Week: Explore Yosemite with Google 🌲" [ref=f11e88] [cursor=pointer]'
187
+ - contentinfo [ref=f11e90]:
188
+ - generic [ref=f11e91]:
189
+ - generic [ref=f11e92]:
190
+ - link "Advertising" [ref=f11e93] [cursor=pointer]:
191
+ - /url: https://www.google.com/intl/en_us/ads/?subid=ww-ww-et-g-awa-a-g_hpafoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpafooter&fg=1
192
+ - link "Business" [ref=f11e94] [cursor=pointer]:
193
+ - /url: https://www.google.com/services/?subid=ww-ww-et-g-awa-a-g_hpbfoot1_1!o2&utm_source=google.com&utm_medium=referral&utm_campaign=google_hpbfooter&fg=1
194
+ - link "How Search works" [ref=f11e95] [cursor=pointer]:
195
+ - /url: https://google.com/search/howsearchworks/?fg=1
196
+ - link "Build, create, and do more with AI tools from Google" [ref=f11e97] [cursor=pointer]:
197
+ - /url: https://ai.google/helpful-tools/?utm_source=googlehpfooter&utm_medium=housepromos&utm_campaign=bottom-footer
198
+ - generic [ref=f11e98]:
199
+ - link "Privacy" [ref=f11e99] [cursor=pointer]:
200
+ - /url: https://policies.google.com/privacy?hl=en&fg=1
201
+ - link "Terms" [ref=f11e100] [cursor=pointer]:
202
+ - /url: https://policies.google.com/terms?hl=en&fg=1
203
+ - button "Settings" [ref=f11e104] [cursor=pointer]
@@ -100,25 +100,119 @@ async function loadPrompt(name, params = {}) {
100
100
  return content;
101
101
  }
102
102
 
103
- /** Create a single-use pi agent session. */
104
- async function createSession({ cwd, apiKey }) {
105
- const modelRuntime = await ModelRuntime.create({
103
+ /**
104
+ * Create a fresh ModelRuntime.
105
+ *
106
+ * Runtime API keys set on this instance are isolated to the current request.
107
+ */
108
+ async function createModelRuntime() {
109
+ return ModelRuntime.create({
106
110
  allowModelNetwork: false,
107
111
  });
112
+ }
113
+
114
+ /**
115
+ * Resolve runtime API keys from the request body.
116
+ *
117
+ * Supports three styles:
118
+ * - `apiKeys`: { "anthropic": "...", "openai": "..." }
119
+ * - `provider` + `apiKey`: set one key for an explicit provider
120
+ * - `apiKey` only (legacy): set key for the first available provider, falling back to anthropic
121
+ */
122
+ function resolveApiKeys(body, availableModels) {
123
+ const keys = [];
124
+
125
+ if (body.apiKeys && typeof body.apiKeys === "object") {
126
+ for (const [providerId, key] of Object.entries(body.apiKeys)) {
127
+ if (typeof key === "string" && key.trim().length > 0) {
128
+ keys.push({ provider: providerId, key });
129
+ }
130
+ }
131
+ return keys;
132
+ }
133
+
134
+ if (body.apiKey && typeof body.apiKey === "string" && body.apiKey.trim().length > 0) {
135
+ const provider = body.provider && typeof body.provider === "string"
136
+ ? body.provider
137
+ : availableModels.length > 0
138
+ ? availableModels[0].provider
139
+ : "anthropic";
140
+ keys.push({ provider, key: body.apiKey });
141
+ }
142
+
143
+ return keys;
144
+ }
108
145
 
109
- // Set API key if provided (uses env vars otherwise)
110
- if (apiKey) {
111
- // Try to set for the first available provider (anthropic preferred)
112
- const available = await modelRuntime.getAvailable();
113
- if (available.length > 0) {
114
- const providerId = available[0].model.provider;
115
- await modelRuntime.setRuntimeApiKey(providerId, apiKey);
146
+ /**
147
+ * Resolve a model object from the request body.
148
+ *
149
+ * Accepts:
150
+ * - string: "anthropic/claude-opus-4-5" or "claude-opus-4-5"
151
+ * - object: { provider: "anthropic", id: "claude-opus-4-5" }
152
+ *
153
+ * If no model is requested, returns undefined so the agent picks a default.
154
+ */
155
+ function resolveModel(modelRuntime, modelSelector, configuredProviders) {
156
+ if (!modelSelector) return undefined;
157
+
158
+ let providerId;
159
+ let modelId;
160
+
161
+ if (typeof modelSelector === "string") {
162
+ const parts = modelSelector.split("/");
163
+ if (parts.length === 2) {
164
+ [providerId, modelId] = parts;
116
165
  } else {
117
- // Blindly try anthropic
118
- await modelRuntime.setRuntimeApiKey("anthropic", apiKey);
166
+ modelId = modelSelector;
167
+ providerId = configuredProviders[0] ?? "anthropic";
119
168
  }
169
+ } else if (typeof modelSelector === "object") {
170
+ providerId = modelSelector.provider;
171
+ modelId = modelSelector.id ?? modelSelector.modelId;
172
+ }
173
+
174
+ if (!providerId || !modelId) {
175
+ throw new Error(`Invalid model selector: ${JSON.stringify(modelSelector)}`);
176
+ }
177
+
178
+ const model = modelRuntime.getModel(providerId, modelId);
179
+ if (!model) {
180
+ throw new Error(`Model not found: ${providerId}/${modelId}`);
120
181
  }
121
182
 
183
+ if (!modelRuntime.hasConfiguredAuth(providerId)) {
184
+ throw new Error(
185
+ `Provider "${providerId}" is not configured with an API key. ` +
186
+ `Provide an apiKey or apiKeys for this provider.`
187
+ );
188
+ }
189
+
190
+ return model;
191
+ }
192
+
193
+ /** Create a single-use pi agent session. */
194
+ async function createSession({ cwd, apiKey, apiKeys, provider, model: modelSelector }) {
195
+ const modelRuntime = await createModelRuntime();
196
+
197
+ // Resolve keys before selecting a model so we know which providers are configured.
198
+ const availableBeforeKeys = modelRuntime.getAvailableSnapshot();
199
+ const keysToSet = resolveApiKeys({ apiKey, apiKeys, provider }, availableBeforeKeys);
200
+
201
+ // Set all runtime API keys in parallel.
202
+ await Promise.all(
203
+ keysToSet.map(({ provider: providerId, key }) =>
204
+ modelRuntime.setRuntimeApiKey(providerId, key).catch((err) => {
205
+ throw new Error(`Failed to set API key for ${providerId}: ${err.message}`);
206
+ })
207
+ )
208
+ );
209
+
210
+ // Refresh availability after keys are set so the model picker sees configured providers.
211
+ await modelRuntime.getAvailable();
212
+
213
+ const configuredProviders = keysToSet.map((entry) => entry.provider);
214
+ const model = resolveModel(modelRuntime, modelSelector, configuredProviders);
215
+
122
216
  const agentDir = getAgentDir();
123
217
 
124
218
  const loader = new DefaultResourceLoader({
@@ -133,6 +227,7 @@ async function createSession({ cwd, apiKey }) {
133
227
  modelRuntime,
134
228
  resourceLoader: loader,
135
229
  sessionManager: SessionManager.inMemory(),
230
+ ...(model ? { model } : {}),
136
231
  });
137
232
 
138
233
  return session;
@@ -185,6 +280,88 @@ app.get("/api/prompts/:name", async (request, reply) => {
185
280
  };
186
281
  });
187
282
 
283
+ // List providers and their auth/configuration status
284
+ app.get("/api/providers", async (request, reply) => {
285
+ try {
286
+ const modelRuntime = await createModelRuntime();
287
+ const providers = modelRuntime.getProviders().map((provider) => ({
288
+ id: provider.id,
289
+ name: provider.name ?? provider.id,
290
+ authStatus: modelRuntime.getProviderAuthStatus(provider.id),
291
+ modelCount: modelRuntime.getModels(provider.id).length,
292
+ }));
293
+ return { providers };
294
+ } catch (err) {
295
+ reply.code(500);
296
+ return { error: `Failed to list providers: ${err.message}` };
297
+ }
298
+ });
299
+
300
+ // List available models. Optionally filter with ?provider=<id>.
301
+ app.get("/api/models", async (request, reply) => {
302
+ try {
303
+ const { provider } = request.query;
304
+ const modelRuntime = await createModelRuntime();
305
+ await modelRuntime.getAvailable();
306
+ const models = modelRuntime.getAvailableSnapshot();
307
+ const filtered = provider
308
+ ? models.filter((model) => model.provider === provider)
309
+ : models;
310
+ return {
311
+ models: filtered.map((model) => ({
312
+ provider: model.provider,
313
+ id: model.id,
314
+ name: model.name ?? model.id,
315
+ thinking: model.thinking,
316
+ })),
317
+ };
318
+ } catch (err) {
319
+ reply.code(500);
320
+ return { error: `Failed to list models: ${err.message}` };
321
+ }
322
+ });
323
+
324
+ // List models available for the provided credentials (per-request keys).
325
+ // Accepts provider+apiKey or an apiKeys map; without keys, lists models from
326
+ // env/stored credentials. Used by clients to validate a key and pick a model.
327
+ app.post("/api/models", async (request, reply) => {
328
+ try {
329
+ const { provider, apiKey, apiKeys } = request.body || {};
330
+ const modelRuntime = await createModelRuntime();
331
+
332
+ const availableBeforeKeys = modelRuntime.getAvailableSnapshot();
333
+ const keysToSet = resolveApiKeys({ provider, apiKey, apiKeys }, availableBeforeKeys);
334
+
335
+ await Promise.all(
336
+ keysToSet.map(({ provider: providerId, key }) =>
337
+ modelRuntime.setRuntimeApiKey(providerId, key).catch((err) => {
338
+ throw new Error(`Failed to set API key for ${providerId}: ${err.message}`);
339
+ })
340
+ )
341
+ );
342
+
343
+ await modelRuntime.getAvailable();
344
+
345
+ let models = modelRuntime.getAvailableSnapshot();
346
+ if (provider) {
347
+ models = models.filter((model) => model.provider === provider);
348
+ }
349
+
350
+ return {
351
+ providers: [...new Set(keysToSet.map((entry) => entry.provider))],
352
+ models: models.map((model) => ({
353
+ provider: model.provider,
354
+ id: model.id,
355
+ name: model.name ?? model.id,
356
+ thinking: model.thinking,
357
+ })),
358
+ };
359
+ } catch (err) {
360
+ reply.code(500);
361
+ return { error: `Failed to list models: ${err.message}` };
362
+ }
363
+ });
364
+
188
365
  // Execute a prompt (SSE streaming)
189
366
  app.post("/api/prompts/:name", async (request, reply) => {
190
367
  const { name } = request.params;
@@ -196,6 +373,9 @@ app.post("/api/prompts/:name", async (request, reply) => {
196
373
 
197
374
  const {
198
375
  apiKey,
376
+ apiKeys,
377
+ provider,
378
+ model,
199
379
  cwd = process.cwd(),
200
380
  stream = true,
201
381
  ...params
@@ -221,7 +401,13 @@ app.post("/api/prompts/:name", async (request, reply) => {
221
401
  // Create session
222
402
  let session;
223
403
  try {
224
- session = await createSession({ cwd: resolve(cwd), apiKey });
404
+ session = await createSession({
405
+ cwd: resolve(cwd),
406
+ apiKey,
407
+ apiKeys,
408
+ provider,
409
+ model,
410
+ });
225
411
  } catch (err) {
226
412
  reply.code(500);
227
413
  return { error: `Failed to create agent session: ${err.message}` };
@@ -313,7 +499,9 @@ try {
313
499
  await app.listen({ port: PORT, host: HOST });
314
500
  console.log(`🦕 Dino API server running at http://${HOST}:${PORT}`);
315
501
  console.log(` Prompts: http://${HOST}:${PORT}/api/prompts`);
502
+ console.log(` Providers: http://${HOST}:${PORT}/api/providers`);
503
+ console.log(` Models: http://${HOST}:${PORT}/api/models`);
316
504
  } catch (err) {
317
505
  app.log.error(err);
318
506
  process.exit(1);
319
- }
507
+ }