Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge branch 'ask-g1t' | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { EMPTY_ASK, type AskState, askPage, askReducer, cutOff, history, readLines, restorable } from "./ask.ts"; | |
| 5 | ||
| 6 | test("the page says which repository, issue or pull request the person is on", () => { | |
| 7 | assert.deepEqual(askPage("/Acme/web/issues/12"), { path: "/Acme/web/issues/12", repo: { namespace: "acme", name: "web" }, issue: 12 }); | |
| 8 | assert.deepEqual(askPage("/acme/web/pull/3/files"), { path: "/acme/web/pull/3/files", repo: { namespace: "acme", name: "web" }, pull: 3 }); | |
| 9 | assert.deepEqual(askPage("/acme/web/tree/main"), { path: "/acme/web/tree/main", repo: { namespace: "acme", name: "web" } }); | |
| 10 | assert.deepEqual(askPage("/acme/web/issues"), { path: "/acme/web/issues", repo: { namespace: "acme", name: "web" } }); | |
| 11 | // A workspace's own pages, and g1t's, name no repository. | |
| 12 | assert.deepEqual(askPage("/acme/-/chat/general"), { path: "/acme/-/chat/general" }); | |
| 13 | assert.deepEqual(askPage("/settings/profile"), { path: "/settings/profile" }); | |
| 14 | assert.deepEqual(askPage("/acme"), { path: "/acme" }); | |
| 15 | assert.deepEqual(askPage(""), { path: "/" }); | |
| 16 | }); | |
| 17 | ||
| 18 | function run(...actions: Parameters<typeof askReducer>[1][]): AskState { | |
| 19 | return actions.reduce(askReducer, EMPTY_ASK); | |
| 20 | } | |
| 21 | ||
| 22 | test("an answer streams into the turn after the question", () => { | |
| 23 | const state = run( | |
| 24 | { type: "ask", question: " What broke? ", id: "1" }, | |
| 25 | { type: "event", event: { type: "meta", provider: "anthropic", provider_label: "Anthropic", model: "claude-x" } }, | |
| 26 | { type: "event", event: { type: "text", text: "The " } }, | |
| 27 | { type: "event", event: { type: "text", text: "login." } }, | |
| 28 | ); | |
| 29 | assert.equal(state.busy, true); | |
| 30 | assert.equal(state.turns[0]!.content, "What broke?"); | |
| 31 | assert.deepEqual( | |
| 32 | { content: state.turns[1]!.content, provider: state.turns[1]!.provider, model: state.turns[1]!.model, status: state.turns[1]!.status }, | |
| 33 | { content: "The login.", provider: "Anthropic", model: "claude-x", status: "streaming" }, | |
| 34 | ); | |
| 35 | const done = askReducer(state, { type: "event", event: { type: "done", stop_reason: "max_tokens" } }); | |
| 36 | assert.equal(done.busy, false); | |
| 37 | assert.equal(done.turns[1]!.status, "done"); | |
| 38 | assert.ok(cutOff(done.turns[1]!)); | |
| 39 | }); | |
| 40 | ||
| 41 | test("one question at a time; empty ones are not asked", () => { | |
| 42 | const asking = run({ type: "ask", question: "a", id: "1" }); | |
| 43 | assert.equal(askReducer(asking, { type: "ask", question: "b", id: "2" }), asking); | |
| 44 | assert.equal(askReducer(EMPTY_ASK, { type: "ask", question: " ", id: "1" }), EMPTY_ASK); | |
| 45 | }); | |
| 46 | ||
| 47 | test("stopping keeps what came; an error says what went wrong; new conversation starts over", () => { | |
| 48 | const stopped = run({ type: "ask", question: "a", id: "1" }, { type: "event", event: { type: "text", text: "par" } }, { type: "stop" }); | |
| 49 | assert.equal(stopped.busy, false); | |
| 50 | assert.deepEqual([stopped.turns[1]!.status, stopped.turns[1]!.content], ["stopped", "par"]); | |
| 51 | // Nothing after the stop lands on the answer. | |
| 52 | assert.equal(askReducer(stopped, { type: "event", event: { type: "text", text: "tial" } }).turns[1]!.content, "par"); | |
| 53 | ||
| 54 | const failed = run({ type: "ask", question: "a", id: "1" }, { type: "event", event: { type: "error", message: "Anthropic did not accept the credentials." } }); | |
| 55 | assert.equal(failed.turns[1]!.error, "Anthropic did not accept the credentials."); | |
| 56 | assert.equal(failed.busy, false); | |
| 57 | assert.equal(run({ type: "ask", question: "a", id: "1" }, { type: "failed", message: "Too many questions." }).turns[1]!.status, "error"); | |
| 58 | assert.equal(askReducer(failed, { type: "reset" }), EMPTY_ASK); | |
| 59 | }); | |
| 60 | ||
| 61 | test("what is sent alternates, leaving out questions whose answer failed", () => { | |
| 62 | const state = run( | |
| 63 | { type: "ask", question: "one", id: "1" }, | |
| 64 | { type: "event", event: { type: "text", text: "first" } }, | |
| 65 | { type: "event", event: { type: "done", stop_reason: "end_turn" } }, | |
| 66 | { type: "ask", question: "two", id: "2" }, | |
| 67 | { type: "event", event: { type: "error", message: "no" } }, | |
| 68 | { type: "ask", question: "three", id: "3" }, | |
| 69 | { type: "event", event: { type: "text", text: "cut" } }, | |
| 70 | { type: "stop" }, | |
| 71 | ); | |
| 72 | assert.deepEqual(history(state.turns, " four "), [ | |
| 73 | { role: "user", content: "one" }, | |
| 74 | { role: "assistant", content: "first" }, | |
| 75 | { role: "user", content: "three" }, | |
| 76 | { role: "assistant", content: "cut" }, | |
| 77 | { role: "user", content: "four" }, | |
| 78 | ]); | |
| 79 | }); | |
| 80 | ||
| 81 | test("lines are read as they arrive, across chunks", () => { | |
| 82 | const first = readLines("", '{"type":"meta","provider":"openai","provider_label":"OpenAI","model":"gpt-5"}\n{"type":"te'); | |
| 83 | assert.equal(first.events.length, 1); | |
| 84 | assert.equal(first.rest, '{"type":"te'); | |
| 85 | const second = readLines(first.rest, 'xt","text":"Hi"}\nnot json\n{"type":"other"}\n'); | |
| 86 | assert.deepEqual(second.events, [{ type: "text", text: "Hi" }]); | |
| 87 | assert.equal(second.rest, ""); | |
| 88 | }); | |
| 89 | ||
| 90 | test("a conversation kept in the tab comes back stopped where it was", () => { | |
| 91 | const kept = restorable({ turns: [{ id: "1-q", role: "user", content: "a", status: "done" }, { id: "1-a", role: "assistant", content: "b", status: "streaming" }], busy: true }); | |
| 92 | assert.equal(kept.busy, false); | |
| 93 | assert.equal(kept.turns[1]!.status, "stopped"); | |
| 94 | assert.deepEqual(restorable("junk"), EMPTY_ASK); | |
| 95 | assert.deepEqual(restorable({ turns: [{ role: "system", content: "x" }] }).turns, []); | |
| 96 | }); |
This file's history is long; its oldest lines are credited to the oldest commit read.