@buyong/pi-devin 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,7 +77,7 @@ Running `/devin` in the TUI shows the following on one screen. Use ↑↓ to mov
77
77
 
78
78
  There are two toggles, both off by default. Changes apply to all sessions at once.
79
79
 
80
- - **Recommended models only**: `/model` shows only the models Devin marks as Recommended, plus Devin Fusion. Recommended models are variants with a fixed thinking level, such as `swe-2-high`, rather than families, so you cannot pick another level with `/thinking`. If your account has no recommended models, the full list is shown.
80
+ - **Recommended models only**: `/model` shows only the models that have a variant Devin marks as Recommended, plus Devin Fusion. Devin marks single variants such as `swe-2-high`, but the list keeps the model itself (`swe-2`), so you still pick the thinking level with `/thinking`. The recommended level is not applied automatically. If your account has no recommended models, the full list is shown.
81
81
  - **Usage in status line**: the TUI footer shows the remaining daily and weekly quota as bars, plus this session's input and output tokens, like `devin day ▕█████████░▏98% week ▕█████████░▏98% · ↑263 ↓49`. A bar turns to the warning color at 40% or less and to the error color at 20% or less.
82
82
 
83
83
  Quota is not live. It is refreshed when you log in and when Pi checks the login right before sending a model request, so the usage of the message you just sent shows up after the next request. `/devin` and the status line send no network requests. Credits, per-request costs and usage history are not shown.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buyong/pi-devin",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Unofficial Pi provider for Devin accounts (OAuth)",
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@ import type { PickerTheme } from "./fusion-picker.ts";
4
4
  import type { UsageLine } from "./usage.ts";
5
5
 
6
6
  export const PREFERENCE_ITEMS: readonly { key: keyof DevinPreferences; label: string; detail: string }[] = [
7
- { key: "recommendedOnly", label: "Recommended models only", detail: "/model shows only recommended variants and Devin Fusion" },
7
+ { key: "recommendedOnly", label: "Recommended models only", detail: "/model shows only models with a recommended variant and Devin Fusion" },
8
8
  { key: "showUsageStatus", label: "Usage in status line", detail: "shows quota and session tokens in the footer" },
9
9
  ];
10
10
  const HELP = "↑↓ move · space/↵ toggle · esc close";
@@ -5,7 +5,7 @@ const SCHEMA_VERSION = 1;
5
5
 
6
6
  /** Display settings toggled by `/devin`. Both default to off and never change how models are used or requested. */
7
7
  export interface DevinPreferences {
8
- /** Lists only the variants that the catalog marks as recommended. */
8
+ /** Lists only the models that have a variant the catalog marks as recommended. */
9
9
  recommendedOnly: boolean;
10
10
  /** Shows quota and session usage in the TUI status line. */
11
11
  showUsageStatus: boolean;
@@ -44,7 +44,8 @@ export function toDevinTranscript(context: TranscriptContext, model: ModelInfo):
44
44
  const declarations = getCurrentTools(context.messages);
45
45
  if (declarations.length && !model.supportsToolCalls) throw new Error("This model does not support the current Pi tool declarations.");
46
46
  const tools = declarations.map((tool) => {
47
- if (tool.constrainedSampling && (tool.constrainedSampling.type === "grammar" || tool.constrainedSampling.strict === "require")) {
47
+ // Grammar sampling is optional; like Pi's providers without grammar tools, send the JSON schema instead.
48
+ if (tool.constrainedSampling && tool.constrainedSampling.type === "json_schema" && tool.constrainedSampling.strict === "require") {
48
49
  throw new Error(`Devin does not support this tool's forced sampling format: ${tool.name}`);
49
50
  }
50
51
  return { name: tool.name, description: tool.description, jsonSchemaString: JSON.stringify(tool.parameters) };
package/src/pi/models.ts CHANGED
@@ -35,7 +35,7 @@ export interface DevinModel extends Model<typeof DEVIN_API> {
35
35
  export interface ProjectionOptions {
36
36
  /** Saved Fusion choice. false (sidekick child Pi) creates no Fusion entry. */
37
37
  fusion?: FusionSelection | false;
38
- /** Lists only variants marked as recommended. The Fusion entry is unaffected. */
38
+ /** Lists only models with a variant marked as recommended. Groups stay groups. The Fusion entry is unaffected. */
39
39
  recommendedOnly?: boolean;
40
40
  /** Also lists exact UIDs that a group already covers. Used only by the sidekick child Pi. */
41
41
  listsAllExactModels?: boolean;
@@ -186,25 +186,26 @@ export function effectiveFusion(projection: VariantProjection, requested: Fusion
186
186
 
187
187
  /**
188
188
  * Exact UIDs covered by a published group are hidden and chosen with `/thinking`. Their catalog configs are stored on the group model.
189
- * `recommendedOnly` lists only recommended variants by exact UID. Without any, it falls back to the group list.
189
+ * `recommendedOnly` keeps only groups and uncovered exact UIDs that hold a recommended variant, so `/thinking` still picks the UID.
190
+ * Without any recommended variant, it falls back to the default list.
190
191
  * `listsAllExactModels` is only for processes that pick exact UIDs directly, such as the sidekick child.
191
192
  */
192
193
  export function modelsFromProjection(projection: VariantProjection, credential: Credential, options: ProjectionOptions = {}): DevinModel[] {
193
194
  const models: DevinModel[] = [], names = groupDisplayNames(projection), ids = publicModelIDs(projection);
194
195
  const published = projection.groups.filter((group) => group.canPublish);
195
- const recommended = projection.variants.filter((variant) => isUsable(variant) && variant.model.isRecommended);
196
+ const recommendedUIDs = new Set(projection.variants.filter((variant) => isUsable(variant) && variant.model.isRecommended).map((variant) => variant.model.id));
196
197
  // Fall back to the default projection so accounts without recommended flags never get an empty list.
197
- const isRecommendedOnly = options.recommendedOnly === true && recommended.length > 0;
198
- if (!isRecommendedOnly) {
199
- for (const group of published) {
200
- const model = modelDefinition(ids.groups.get(group.id)!, names.get(group.id)!, publishedVariants(group), group.mappings, credential);
201
- if (group.variants.some((variant) => variant.model.isRecommended)) model.devin.recommended = true;
202
- models.push(model);
203
- }
198
+ const isRecommendedOnly = options.recommendedOnly === true && recommendedUIDs.size > 0;
199
+ const isListed = (uids: readonly string[]) => !isRecommendedOnly || uids.some((uid) => recommendedUIDs.has(uid));
200
+ for (const group of published) {
201
+ if (!isListed(Object.values(group.mappings))) continue;
202
+ const model = modelDefinition(ids.groups.get(group.id)!, names.get(group.id)!, publishedVariants(group), group.mappings, credential);
203
+ if (group.variants.some((variant) => variant.model.isRecommended)) model.devin.recommended = true;
204
+ models.push(model);
204
205
  }
205
206
  const covered = new Set(published.flatMap((group) => Object.values(group.mappings)));
206
- const exact = isRecommendedOnly ? recommended
207
- : projection.variants.filter((variant) => isUsable(variant) && (options.listsAllExactModels === true || !covered.has(variant.model.id)));
207
+ const exact = projection.variants.filter((variant) => isUsable(variant) && isListed([variant.model.id])
208
+ && (options.listsAllExactModels === true || !covered.has(variant.model.id)));
208
209
  const listedNames = new Set(models.map((model) => model.name));
209
210
  for (const variant of exact) {
210
211
  const name = listedNames.has(variant.model.name) ? `${variant.model.name} (${variant.model.id})` : variant.model.name;