@likelz/pi-gpt 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -34,11 +34,22 @@ Edit `~/.pi/agent/extensions/pi-gpt.json` (under `PI_CODING_AGENT_DIR` when over
34
34
  ```json
35
35
  {
36
36
  "enabled": false,
37
- "models": ["openai/gpt-6-astra"]
37
+ "models": ["/[/]gpt-6-astra$/"]
38
38
  }
39
39
  ```
40
40
 
41
- Use the exact `provider/model` identifiers shown by Pi, including any additional slashes in a model ID. The list starts empty; add models whose endpoint supports priority processing. Restart Pi or reload extensions after editing the file.
41
+ Entries are either exact `provider/model` identifiers or JavaScript regular expressions wrapped in `/.../` (without flags). Regex rules run against the complete identifier, including additional slashes in the model ID. They use normal regex search semantics; add `^` and `$` where you need boundaries.
42
+
43
+ | Rule | Matches |
44
+ | ------------------- | -------------------------------------------------------------------------------- |
45
+ | `/[/]gpt-6-astra$/` | `openai/gpt-6-astra`, `openai-codex/gpt-6-astra`, and `magpie/codex/gpt-6-astra` |
46
+ | `/^openai[/]gpt-/` | GPT model IDs under the `openai` provider |
47
+ | `openai/gpt-5.4` | Only this exact identifier |
48
+ | `/.*/` | Every model |
49
+
50
+ `[/]` matches a literal slash, so the first rule requires the exact final model name and excludes `gpt-6-astra-pro`. Backslashes need JSON escaping: use `"/gpt-5\\.4$/"` to match a literal dot. Plain strings remain literal; `*` has regex meaning only inside `/.../` rules. Invalid regex configuration produces a warning and starts with fast mode disabled.
51
+
52
+ The list starts empty; add rules for models whose endpoints support priority processing. Restart Pi or reload extensions after editing the file.
42
53
 
43
54
  - `/fast` toggles and persists `enabled`.
44
55
  - Unlisted models and disabled mode leave requests unchanged.
@@ -56,6 +67,24 @@ This extension requests **priority**, preserving the original behavior. OpenAI a
56
67
 
57
68
  Sources: [Ultrafast API](https://developers.openai.com/api/docs/guides/ultrafast-mode), [Fast API](https://developers.openai.com/api/docs/guides/fast-mode), [Codex speed and eligibility](https://learn.chatgpt.com/docs/agent-configuration/speed), [Codex pricing](https://learn.chatgpt.com/docs/pricing).
58
69
 
70
+ ## Publishing
71
+
72
+ [Publish to npm](https://github.com/ekil1100/pi-gpt/actions/workflows/publish.yml) uses GitHub Actions OIDC. It needs no npm token secret. Node.js 24, npm 11.19.0, and Bun 1.4.2 are configured in the workflow. The `prepublishOnly` script runs checks, tests, and the build before publishing.
73
+
74
+ Configure a GitHub Actions trusted publisher in the [npm package settings](https://www.npmjs.com/package/@likelz/pi-gpt/access):
75
+
76
+ | Field | Value |
77
+ | -------------------- | ------------------------------------------ |
78
+ | Organization or user | `ekil1100` |
79
+ | Repository | `pi-gpt` |
80
+ | Workflow filename | `publish.yml` |
81
+ | Environment name | Leave empty |
82
+ | Allowed actions | Allow direct publishing with `npm publish` |
83
+
84
+ Run the workflow manually to check installation, validation, and tarball contents without uploading. This dry run does not verify npm's OIDC trust configuration; that is exercised by an actual release.
85
+
86
+ To release, update `package.json` to a new stable version, commit and push it, then push a matching tag such as `v0.1.1`. The workflow rejects tags that do not match the package version. Version `0.1.0` is already published and cannot be republished. OIDC publishing automatically includes provenance for this public repository and package.
87
+
59
88
  ## Development
60
89
 
61
90
  Use Bun 1.4.2 and Vite+ 1.0.0. Development was verified on Node.js 24.
package/dist/index.mjs CHANGED
@@ -3,9 +3,20 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
3
3
  import { Effect, Either, Schema } from "effect";
4
4
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  //#region src/state.ts
6
+ const isRegex = (rule) => rule.startsWith("/");
7
+ const isValidRule = (rule) => {
8
+ if (!isRegex(rule)) return /^[^/\s]+\/[^\s]+$/.test(rule);
9
+ if (rule.length < 3 || !rule.endsWith("/")) return false;
10
+ try {
11
+ new RegExp(rule.slice(1, -1));
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ };
6
17
  const FastModeState = Schema.Struct({
7
18
  enabled: Schema.Boolean,
8
- models: Schema.Array(Schema.String.pipe(Schema.pattern(/^[^/\s]+\/[^\s]+$/)))
19
+ models: Schema.Array(Schema.String.pipe(Schema.filter(isValidRule, { message: () => "Expected a provider/model identifier or a valid /regex/ rule." })))
9
20
  });
10
21
  const emptyState = () => ({
11
22
  enabled: false,
@@ -13,9 +24,10 @@ const emptyState = () => ({
13
24
  });
14
25
  const decodeState = Schema.decodeUnknownSync(Schema.parseJson(FastModeState));
15
26
  const modelKey = (model) => model ? `${model.provider}/${model.id}` : void 0;
27
+ const matchesModel = (rule, key) => isRegex(rule) ? new RegExp(rule.slice(1, -1)).test(key) : rule === key;
16
28
  const isActive = (state, model) => {
17
29
  const key = modelKey(model);
18
- return state.enabled && key !== void 0 && state.models.includes(key);
30
+ return state.enabled && key !== void 0 && state.models.some((pattern) => matchesModel(pattern, key));
19
31
  };
20
32
  const transformRequest = (state, model, payload) => isActive(state, model) && typeof payload === "object" && payload !== null && !Array.isArray(payload) ? {
21
33
  ...payload,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likelz/pi-gpt",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "GPT fast mode for Pi with explicit model allowlists.",
5
5
  "keywords": [
6
6
  "gpt",
package/src/state.ts CHANGED
@@ -1,8 +1,27 @@
1
1
  import { Schema } from "effect";
2
2
 
3
+ const isRegex = (rule: string): boolean => rule.startsWith("/");
4
+
5
+ const isValidRule = (rule: string): boolean => {
6
+ if (!isRegex(rule)) return /^[^/\s]+\/[^\s]+$/.test(rule);
7
+ if (rule.length < 3 || !rule.endsWith("/")) return false;
8
+ try {
9
+ new RegExp(rule.slice(1, -1));
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ };
15
+
3
16
  export const FastModeState = Schema.Struct({
4
17
  enabled: Schema.Boolean,
5
- models: Schema.Array(Schema.String.pipe(Schema.pattern(/^[^/\s]+\/[^\s]+$/))),
18
+ models: Schema.Array(
19
+ Schema.String.pipe(
20
+ Schema.filter(isValidRule, {
21
+ message: () => "Expected a provider/model identifier or a valid /regex/ rule.",
22
+ }),
23
+ ),
24
+ ),
6
25
  });
7
26
  export type FastModeState = typeof FastModeState.Type;
8
27
  export const emptyState = (): FastModeState => ({ enabled: false, models: [] });
@@ -12,9 +31,14 @@ type Model = { readonly provider: string; readonly id: string } | undefined;
12
31
  export const modelKey = (model: Model): string | undefined =>
13
32
  model ? `${model.provider}/${model.id}` : undefined;
14
33
 
34
+ const matchesModel = (rule: string, key: string): boolean =>
35
+ isRegex(rule) ? new RegExp(rule.slice(1, -1)).test(key) : rule === key;
36
+
15
37
  export const isActive = (state: FastModeState, model: Model): boolean => {
16
38
  const key = modelKey(model);
17
- return state.enabled && key !== undefined && state.models.includes(key);
39
+ return (
40
+ state.enabled && key !== undefined && state.models.some((pattern) => matchesModel(pattern, key))
41
+ );
18
42
  };
19
43
 
20
44
  export const transformRequest = (